標籤:
我在前面多篇部落格中詳細講解了CollectionView的使用與自訂CollectionViewCell的設計,可以參考《iOS開發實戰——CollectionView點擊事件與鍵盤隱藏結合案例》《iOS進階開發——CollectionView修改cell的文本及模型重構》這幾篇部落格。但是今天還是需要來講講CollectionView實現中的一個小小的坑,這是我最近在網上瀏覽時發現很多開發人員經常犯的錯,所以我覺得有必要來好好談一談。
一個CollectionView控制項中,兩個cell之間的間距如何設定?這是一個很常見的問題。當我們在網上搜這個問題的時候,很多人告訴你使用UICollectionViewDelegateFlowLayout這個代理中的minimumInteritemSpacingForSectionAtIndex方法來設定。其實這完全錯了,當你真的用這個方法去設定間距的時候,發現怎麼都設定不成自己的需求,真的是大相徑庭。本篇部落格就要來解決這個問題,範例程式碼上傳至 https://github.com/chenyufeng1991/SpaceOfCollectionView 。
(1)首先自訂一個CollectionViewCell,繼承自UICollectionViewCell。在這個cell中放一張圖片填充,該檔案定義為CustomCollectionViewCell.
CustomCollectionViewCell.h檔案如下:
#import <UIKit/UIKit.h>#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)#define CELL_WIDTH01 (SCREEN_WIDTH - 80) / 3#define CELL_WIDTH02 70@interface CustomCollectionViewCell : UICollectionViewCell@property (nonatomic, strong) UIImageView *imageView;@end
CustomCollectionViewCell.m檔案如下:
#import "CustomCollectionViewCell.h"#import "Masonry.h"@implementation CustomCollectionViewCell- (instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, CELL_WIDTH02, CELL_WIDTH02)]; [self addSubview:self.imageView]; // 圖片填充滿整個cell [self.imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self); }]; } return self;}@end
上面的宏定義SCREEN_WIDTH表示螢幕寬度。CELL_WIDTH01和CELL_WIDTH02可以用來設定cell的寬高。
(2)我的主檔案為MainViewController.m,首先需要聲明三個代理:
UICollectionViewDelegate,
UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout
(3)MainViewController.m中聲明兩個屬性:
@property (nonatomic, strong) UICollectionView *collectionView;@property (nonatomic, strong) NSMutableArray *collArr;
(4)宏定義幾個變數:
#define ARRAY_COUNT 10#define MINIMUM_ITEM_SPACE 5#define MINIMUM_LINE_SPACE 5
ARRAY_COUNT是cell的數量;
MINIMUM_ITEM_SPACE是設定cell之間的最小間距(等下我解釋這個概念)
MINIMUM_LINE_SPACE是設定每行之間的間距。
(5)UI布局
- (void)configUI{ // CollectionView self.collArr = [[NSMutableArray alloc] init]; for (int i = 0; i < ARRAY_COUNT; i++) { [self.collArr addObject:[UIImage imageNamed:@"beauty"]]; } UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];// CollectionView的滾動方向,只能二選一 // 初始化,可以不設定寬高,通過下面的mas_makeConstraints自動布局完成。 self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 100, SCREEN_WIDTH, CELL_WIDTH02 * 2) collectionViewLayout:flowLayout]; self.collectionView.bounces = NO; [self.collectionView registerClass:[CustomCollectionViewCell class] forCellWithReuseIdentifier:@"CollectionCell"]; self.collectionView.delegate = self; self.collectionView.dataSource = self; [self.view addSubview:self.collectionView]; [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.view).offset(64); make.left.equalTo(self.view); make.right.equalTo(self.view); make.height.equalTo(@(CELL_WIDTH02 * 2)); }];}
cell的數量可以通過宏定義ARRAY_COUNT來設定。
這裡的Autolayout我使用Masonry進行自動布局,關於Masonry的使用可以參考《Autolayout第三方庫Masonry的入門與實踐》。
這裡我設定UICollection的寬度為螢幕寬度,高度為2 * CELL_WIDTH02.
UICollectionViewFlowLayout是設定CollectionView內部cell的布局,必須進行設定。
(6)UICollectionViewDataSource代理中方法重寫:
#pragma mark - UiCollectionViewDataSource- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ return self.collArr.count;}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath]; // 重用cell if (cell == nil) { // 當重用cell為空白時,建立新的cell cell = [[CustomCollectionViewCell alloc] init]; } cell.imageView.image = self.collArr[indexPath.row]; return cell;}
numberOfItemsInSection是設定section中cell的數量,返回數組數量即可,在這裡其實就是ARRAY_COUNT宏定義。
cellForItemAtIndexPath是為cell進行賦值。
(7)UICollectionViewDelegateFlowLayout代理中方法的重寫:
#pragma mark - UICollectionViewDelegateFlowLayout// 該方法是設定cell的size- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{ return CGSizeMake(CELL_WIDTH02, CELL_WIDTH02);}// 該方法是設定一個section的上左下右邊距- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{ // 注意,這裡預設會在top有+64的邊距,因為狀態列+導覽列是64. // 因為我們常常把[[UIScreen mainScreen] bounds]作為CollectionView的地區,所以蘋果API就預設給了+64的EdgeInsets,這裡其實是一個坑,一定要注意。 // 這裡我暫時不用這個邊距,所以top減去64 // 所以這是就要考慮你是把Collection從螢幕左上方(0,0)開始放還是(0,64)開始放。 return UIEdgeInsetsMake(-64, 0, 0, 0);}// 兩個cell之間的最小間距,是由API自動計算的,只有當間距小於該值時,cell會進行換行- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{ return MINIMUM_ITEM_SPACE;}// 兩行之間的最小間距- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section{ return MINIMUM_LINE_SPACE;}
-- sizeForItemAtIndexPath方法是設定cell的寬高,這裡我設定成CELL_WIDTH02;
-- insetForSectionAtIndex方法是設定一個section在CollectionView中的內邊距;
-- minimumInteritemSpacingForSectionAtIndex方法是設定cell之間的最小邊距(我下面會詳細講解這個方法);
-- minimumLineSpacingForSectionAtIndex方法是設定每行之間的距離;
(8)完成以上代碼後 ,實現的效果如下:
。
---------------------------------------------------------------------------------------------------------(打個分割線,其實以上都是鋪墊,下面正式講如何設定cell之間的邊距)
其實準確的說,cell之間的間距不是我們手動設定的,也沒有辦法手動設定,是由系統為我們計算的。計算方式如下:
CollectionView寬度:CollectionWidth,
一個Cell寬度:CellWidth,
一行cell的個數:N,
cell的間距:SpaceX,
cell的最小間距:MinimumX
公式如下:SpaceX = CollectionWidth - CellWidth * N(N為正整數); 當 SpaceX >= MinimumX時,N會遞增,也就是說一行的cell數量會增多。系統會進行檢測,當增加一個cell時,SpaceX < MinimumX時(實際間距絕對不能小於這個最小間距),cell就會進行換行,所以N會取允許範圍內的最大值。由此可見,對於某一個固定的CollectionView,cell的間距只是由cell的寬度決定的。
在我上面的例子中,是在5s模擬器下測試的,螢幕寬度為320,cell寬度為70,minimumInteritemSpacingForSectionAtIndex方法設定的最小cell間距為5,所以一行只能放下4個cell,並且cell之間的間距=(320-70*4)/3 = 13.33 > 5 .並且不能放5個,因為 5 * 70 > 320了。當然cell也不可能為3個,因為可以設定的最大值為4個。我這裡通過來驗證這個是否正確:
。
通過驗證,我們的計算是正確的,因為是兩倍像素:所以2*13 = 26.
好,為了再次說明我的說法的正確性,minimumInteritemSpacingForSectionAtIndex方法的傳回值可以為13以下的任何正整數,最後啟動並執行UI都是一樣的。因為當cell=4的時候,系統計算的13.33始終大於minimumInteritemSpacingForSectionAtIndex傳回值。
現在修改代碼,minimumInteritemSpacingForSectionAtIndex傳回值為14,重新運行程式,UI效果如下:
// 兩個cell之間的最小間距,是由API自動計算的,只有當間距小於該值時,cell會進行換行- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{ return 14.0f;}
。
可以看到一行的cell只有3個了。因為當一行cell=4的時候,間距13.33小於了我設定的最小間距14,所以系統不得不一行減少一個cell來滿足這個最小間距的要求。現在計算一下間距:(320 - 3 * 70) /2 = 55 > 14最小間距,滿足條件, 通過來進行驗證:
。
實際證明計算仍舊是正確的。
現在大家應該明白了minimumInteritemSpacingForSectionAtIndex這個方法的含義了吧,也知道了如何去調整cell的間距。所以綜上所述一下,對於控制CollectionView中一行顯示的cell數量和cell間距,只能通過cell的寬高和minimumInteritemSpacingForSectionAtIndex設定最小間距來完成,而不是通過代碼手動設定的,計算任務就交給系統吧。
iOS開發實戰——CollectionView中cell的間距設定