在很多app中都有這樣通用的頁面,一直沒有機會使用UICollectionView
,只是簡單的看過他的使用方法。今天公司美工出圖,使用了他,并且遇到了好多的坑。記錄一下過程,不確定使用的方法是不是最優的,如果有更好的方案,一起討論,一起進步
理論篇
一.UICollectionViewLayout是做什么的?
1.1 在創建UITableView
的時候,使用的是- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
用于判斷是普通還是分組,
1.2 UICollectionViewLayout
實際的作用是一樣的,是用來設置cell的布局的,初始化collectionView
的時候,一定要給他設置這個屬性,否者不會顯示。UICollectionViewFlowLayout
是UICollectionViewLayout
的子類,給collectionView賦值的時候,一定要使用** UICollectionViewFlowLayout**初始化。
1.3 UICollectionViewFlowLayout
和UICollectionViewLayout
的關系就像是UIGestureRecognizer
和UITapGestureRecognizer
的一樣。一個是父類,一個是子類。使用的時候都用子類
二. UICollectionViewLayout的屬性
如果都是固定的,建議生成layout對象的時候,設置全局屬性,(其布局很有意思,當你的cell設置大小后,一行多少個cell,由cell的寬度決定)
NS_CLASS_AVAILABLE_IOS(6_0) @interface UICollectionViewFlowLayout : UICollectionViewLayout
//每行之間豎直之間的最小間距 (可以大于)
@property (nonatomic) CGFloat minimumLineSpacing;
//同行的cell與cell之間水平之間的最小間距(可以)
@property (nonatomic) CGFloat minimumInteritemSpacing;
//每個cell的尺寸,如果都是上圖的那種,整個collectionView都是同一種,那么可以用整個屬性,如果想我們公司那樣的樣式,不建議設置該屬性
@property (nonatomic) CGSize itemSize;
//預估cell的尺寸,ios8之后可以先去預估cell的尺寸,然后去自適應
@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0); // defaults to CGSizeZero - setting a non-zero size enables cells that self-size via -perferredLayoutAttributesFittingAttributes:
//滑動的方向,水平或者豎直,看到很多圖片瀏覽器都是用collectionview做出來的(注冊之后,可以復用),非常的好用!但是要記住,水平滑動只有collectionview有,tableview不支持的,默認豎直方法滑動
@property (nonatomic) UICollectionViewScrollDirection scrollDirection; // default is UICollectionViewScrollDirectionVertical
//組頭組尾的size
@property (nonatomic) CGSize headerReferenceSize;
@property (nonatomic) CGSize footerReferenceSize;
//組的四周切的范圍
@property (nonatomic) UIEdgeInsets sectionInset;
@end
minimumLineSpacing 屬性詳解
sectionInset 屬性詳解
注意,我剛才說的,如果所有的cell都是一樣尺寸,我們可以設置初始化layout之后,直接賦值,如果想我們公司那樣,隨意可能改變,建議看看下邊的代理方法
三. UICollectionViewLayout的代理方法
3.1 過去我們使用UITableView
的時候,直接聲明數據源方法,和代理方法,
3.2 使用UICollectionView
的時候,也要聲明兩個。
1.UICollectionViewDelegateFlowLayout
2.UICollectionViewDataSource
,
因為1中包含了3.UICollectionViewDelegate
,所以可以省略3
#pragma mark - UICollectionViewDelegateFlowLayout
//每個cell的大小,因為有indexPath,所以可以判斷哪一組,或者哪一個item,可一個給特定的大小,等同于layout的itemSize屬性
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(34,56);
}
// 設置整個組的縮進量是多少
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(5, 5, 5, 5);
}
// 設置最小行間距,也就是前一行與后一行的中間最小間隔
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 10;
}
// 設置最小列間距,也就是左行與右一行的中間最小間隔
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 10;
}
// 設置section頭視圖的參考大小,與tableheaderview類似
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(self.view.frame.size.width, 40);
}
// 設置section尾視圖的參考大小,與tablefooterview類似
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
return CGSizeMake(self.view.frame.size.width, 40);
}
四. UICollectionView的組頭和組尾(頁眉和頁腳)
1.
UICollectionView
中非常明確是以組為單位,可以設置組的組頭和尾巴,這里的頭尾還可以復用
2.復用的時候,首先頭尾view要繼承于
UICollectionReusableView
,然后注冊(分為nib和class兩種)
3.用的時候通過collectionView去dequeue一下獲取,和cell的思路一樣
4.可以使用上文中的layout屬性直接設置組頭和組尾的size,也可以使用代理方法,去設置
五. UICollectionView的數據源方法
和tableview的數據源方法一樣,想要成為其數據源,然后聲明數據源
#pragma mark - UICollectionViewDataSource
// 指定Section個數
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 3;
}
// 指定section中的collectionViewCell的個數
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 10;
}
// 配置section中的collectionViewCell的顯示
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CellIdentifier" forIndexPath:indexPath];
cell.backgroundColor = [UIColor redColor];
cell.textLabel.text = [NSString stringWithFormat:@"(%ld %ld)", indexPath.section, indexPath.row];
return cell;
}
六. UICollectionView的代理方法
#pragma mark - UICollectionViewDelegate
// 允許選中時,高亮
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __FUNCTION__);
return YES;
}
// 高亮完成后回調
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __FUNCTION__);
}
// 由高亮轉成非高亮完成時的回調
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __FUNCTION__);
}
// 設置是否允許選中
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __FUNCTION__);
return YES;
}
// 設置是否允許取消選中
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __FUNCTION__);
return YES;
}
// 選中操作
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __FUNCTION__);
}
// 取消選中操作
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%s", __FUNCTION__);
}
實戰篇
一. 將設計圖分解成合理結構
分解原因及說明
0.創建控制器(繼承自UICollectionViewController),然后創建基本的layout,給某些固定的數據賦值
UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumInteritemSpacing = 0;
layout.minimumLineSpacing = 9;
layout.sectionInset = UIEdgeInsetsMake(0, 9, 0, 9);
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
THFindController * discoverVC = [[THFindController alloc] initWithCollectionViewLayout:layout];
discoverVC.title = @"發現";
1.說了一頓,特意說明,
UICollectionView
是很強調組這個概念,有組頭,組尾這兩個概念,但一直沒有提到tableHeaderView這樣的控件,所以我們將1(輪播圖)+2(兩個按鍵view)+ 間隔+3(精選動態)封裝成第一組的headerView(封裝的類名是THFineAdView),繼承自UICollectionReusableView(繼承自UIView,沒啥功能,除了復用)
2.將5也集成字
UICollectionReusableView
封裝一下
3.封裝完畢之后,要去注冊一下,注冊的使用,分為nib,和class注冊
3.1 第一組的headerView是同純代碼封裝的,所以注冊的時候這樣
[self.collectionView registerClass:[THFineAdView class]
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:kTHFindAdViewIden];
3.2 第二組的headerView使用的是nib方式,所以也要注冊一下
UINib * nib = [UINib nibWithNibName:@"THFindStyleHeaderView" bundle:nil];
[self.collectionView registerNib:nib
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:kTHFindStyleHeaderViewIden];
3.3 (模塊4和模塊5之間的間隙,模塊6和模塊7之間的間隙)可以通過sectionInset來實現,但是我認為成為組1,組2的sectionFooter更加靠譜一些。那就注冊一下
[self.collectionView registerClass:[THFindSectionFooterView class]
forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:kFooterViewIden];
注意
UICollectionElementKindSectionHeader
這個代表頭的意思,如果注冊尾巴,使用UICollectionElementKindSectionFooter
注冊的三個方法應該寫在一起
4.調用組頭和組尾
#pragma mark - collectionview的代理方法
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath{
//先通過kind類型判斷是頭還是尾巴,然后在判斷是哪一組,如果都是一樣的頭尾,那么只要第一次判斷就可以了
if (kind == UICollectionElementKindSectionHeader){
if (indexPath.section == 0) {
THFineAdView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:kTHFindAdViewIden
forIndexPath:indexPath];
view.bannerArr = self.bannerArr;
return view;
}
else if(indexPath.section == 1){
THFindStyleHeaderView * view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:kTHFindStyleHeaderViewIden
forIndexPath:indexPath];
view.titleLab.text = @"推薦用戶";
return view;
}
}
else{
UICollectionReusableView *footer = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:kFooterViewIden
forIndexPath:indexPath];
return footer;
}
return nil;
}
5.調用組頭和組尾的高度
設置頭和尾的size,要用兩個代理方法,使用代理方法的好處在于可以分情況判斷
// 設置section頭視圖的參考大小,與tableheaderview類似
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout
referenceSizeForHeaderInSection:(NSInteger)section {
if(section == 0){
return CGSizeMake(ScreenWidth, [THFineAdView adViewHeight]);
}else if(section == 1){
return CGSizeMake(ScreenWidth, [THFindStyleHeaderView findStyleHeight]);
}else{
return CGSizeZero;
}
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
referenceSizeForFooterInSection:(NSInteger)section{
return CGSizeMake(ScreenWidth, 10*THScreenScaleNum);
}
6.數據源方法
#pragma mark <UICollectionViewDataSource>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
NSInteger pre = (self.preArr.count != 0);
NSInteger next = (self.nextArr.count != 0);
NSInteger users = (self.userArr.count != 0);
return pre+next+users;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if (section == 0) {
return 4;
}else if(section == 1){
return 1;
}else{
return self.nextArr.count;
}
return 0;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell * cell = nil;
if (indexPath.section == 0) {
THRecommendCell *recCell = [THRecommendCell recommendCellWithCollectionView:collectionView indePath:indexPath];
recCell.twitterM = self.preArr[indexPath.item];
cell = recCell;
}else if (indexPath.section == 1){
THRecommendUsersCell * userCell = [THRecommendUsersCell cellWithColletionView:collectionView indexPath:indexPath];
userCell.users = self.userArr;
cell = userCell;
}else{
THRecommendCell *rCell = [THRecommendCell recommendCellWithCollectionView:collectionView indePath:indexPath];
rCell.twitterM = self.nextArr[indexPath.item];
cell = rCell;
}
return cell;
}
7.在使用自定義cell之前一定要注冊,否者不能復用,給系統造成很大的壓力,經常卡頓
//我是自定義了一個方法,傳遞indexPAth和collectionview直接注冊
+ (instancetype)recommendCellWithCollectionView:(UICollectionView *)collectionView
indePath:(NSIndexPath *)indexPath{
[collectionView registerClass:[self class] forCellWithReuseIdentifier:@"THRecommendCell"];
return [collectionView dequeueReusableCellWithReuseIdentifier:@"THRecommendCell" forIndexPath:indexPath];
}
8.如何自定義cell
他的自定義非常簡單,就幾個方法
#pragma mark - 直接寫這個方法
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self createSub];
}
return self;
}
- (void)createSub{
self.contentView.backgroundColor = [UIColor whiteColor];
//1.圖片
[self.contentView addSubview:self.iconImage];
//2.題目
[self.contentView addSubview:self.titleLab];
//3.喜歡數
[self.contentView addSubview:self.likeBtn];
//4.評論數
[self.contentView addSubview:self.recommentBtn];
}
#pragma mark - 布局
- (void)updateConstraints{
[super updateConstraints];
//圖片
}
如果是xib加載的話,最多有個awakeFromNib
和view的一樣使用
9.代理方法,就懶得寫了
如果各位同行有什么好的建議,可以告訴我,我會虛心接受,再次修改本文的,一起進步~ 順便給有個好文章,可以看看 參考文檔