UICollectionView基礎學習
iOS6引入的集合(CollectionView)是一種網格狀視圖, 用來排布其中的各個單元格, 他們不僅僅是能夠垂直滾動的一列單元格. 集合視圖的許多概念與表格相同, 當卻更強大, 也更加靈活
本次將介紹集合視圖的一些基本知識, 包括他的數據源, 特殊用途, 以及單元格等. 同學們將會學到如何開發標準的集合視圖與自定義的集合視圖, 如何在視圖中添加特效, 以及如是使用內置的動畫功能來創建相當高效的交互方式
1.1 集合視圖與表視圖的異同
UICollectionView 實例會把各項數據展示成一份有序集合. 與表視圖一樣, 集合視圖也有單元格, 頭部及尾部構成, 而且由數據源及委托驅動, 但與表格不同的地方在于, 集合視圖還引入了與布局有關的類, 這個類用來指定各個條目應該如何擺放在屏幕上, 該類負責管理各個單元格的位置, 使得對應的條目可以在必要時出現在適當的位置
對比項 | 集合視圖 | 表格 |
---|---|---|
主類 | UICollectionView | UITableView |
控制器 | UICollectionViewController | UITableViewController |
內容 | 單元格,補充視圖(頭部或者尾部), 裝飾視圖(背景圖版及視覺裝飾) | 單元格, 頭部, 尾部 |
由用戶所觸發的重新載入 | reloadData | reloadData |
可復用單元格 | UICollectionViewCell(通過 dequeueReuseableCellWithReuseIdentifier:forIndexPath: 獲取) | UITableViewCell(通過 dequeueReuseableCellWithReuseIdentifier:forIndexPath: 獲取) |
注冊 | 類或 XIB 來注冊可復用單元格, 補充視圖以及裝飾圖 | 用類或 XIB 來注冊可復用單元格 |
頭部及尾部 | UICollectionReusableView | UITableViewHeaderView, UITableViewFooterView |
布局 | UICollectionViewLayout 及 UICollectionViewFlowLayout | 不適用 |
數據源 | UICollectionDataSource | UITableViewDataSource |
委托 | UICollectionViewDelegate | UITableViewDelegate |
用于布局的委托 | UICollectionViewDelegateFlowlayout | 不適用 |
索引機制 | 通過區段與條目索引 | 通過區段與行來定位 |
滾動方向 | 水平或垂直 | 垂直 |
視覺效果 | 自定義設置 | 不適用 |
1.2 兩者在實現層的區別
編寫程序時, 構建表格視圖和構建集合視圖所用的代碼是有幾個地方不同的. 集合視圖不允許相互局的延遲加載. 在創建集合視圖時, 為該視圖提供內容的數據源必須按時完全準備好的. 我們不能等到程序執行初始化方法, loadView 方法或者 viewDidLoad 方法的時候在準備數據源, 而是必須把他準備好.
可以再應用程序委托中準備, 也可以在實例化集合視圖并將它添加到其他試圖之前, 或者把集合視圖控制器設置為其他控制器的子控制器之前
展示集合視圖之前, 一點要把集合視圖的布局對象完全準備好
MyCollectionViewController *mcc = [[MyCollectionViewController alloc] initWithCollectionViewLayout:layout];
集合視圖的生命周期中并不只能有一種布局, 可以使用 collectionViewLayout 屬性來訪問集合視圖的布局, 修改這個屬性之后, 視圖就會以不帶動畫方式立即更新其布局, iOS7 提供了一個簡單帶動畫的布局切換方式
- (void)setCollectionViewLayout:animated:completion:
- 建立集合視圖
2.1 通過控制器使用集合視圖
構建控制器時, 首先創建并設置號其布局對象, 然后分配新的實例, 并用準備好的布局對象初始化
UICollectionViewFlowLayout *layout = [[UICollectionFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; // 修改滾動方向
MyCollectionViewController *mcc = [[MyCollectionViewController alloc] initWithCollectionViewLayout:layout];
2.2 直接使用集合視圖
UICollectionViewFlowLayout *layout = [[UICollectionFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; // 修改滾動方向
collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
collectionView.dataSource = self;
collectionView.delegate = self;
2.3 數據源與委托
管理集合視圖的視圖控制器會宣稱自己實現了 UICollectionViewDataSource, UICollectionViewDelegate 協議, 如果使用 FlowLayout 布局控制器還會宣稱實現了 UICollectionViewDelegateFlowLayout 協議, 該協議通過一系列回調方法來個集合視圖的布局對象提供布局信息
與表視圖一樣, 數據源提供了每個區段以及區段內每個條目的信息, 并根據需求返回對應的單元格以及集合視圖上面的其他部件, 委托處理用戶操作, 并對用戶的改動請求作出回應, UICollectionViewDelegateFlowLayout 則負責提供該區段的詳細的布局信息, 大多數情況下, 它的方法都是可選的
- 流式布局
3.1 滾動方向: scrollDirection 屬性決定了視圖中的區段是水平排列還是垂直排列, 水平滾動: 元素布局時會優先將垂直方向的空間填充完畢,然后開啟下一列; 垂直滾動則優先將水平空間填充完畢,然后開啟下一行
3.2 條目尺寸,行間距: minimumLineSpacing, minimumInteritemSpacing 屬性規定了每個區段內條目之間的距離; 行間距( line spacing)表示連個相鄰行之間的距離, 在水平布局下是垂直方向, 在垂直布局下時水平方向
3.3 頭部與尾部尺寸: headerReferenceSize, footerReferenceSize 屬性定義了區段的頭部和尾部應該有多寬, 多高, 接收的值為 CGSize, 當為水平布局時使用寬度字段, 垂直布局時, 使用高度字段
以上2,3屬性均可以在 UICollectionViewDelegateFlowLayout 中設置
collectionView:layout:sizeForItemAtIndexPath --- 方法與 itemSize 屬性相對應, 他可以指定具體每個條目尺寸
collectionView:layout:minimumLineSpacingForSectionAtInde: --- 方法與 minimumLineSpacing 屬性對應, 他可以具體到每個區段內的最小行間距
collectionView:layout:minimumInteritemSpacingForSectionAtInde: --- 方法與 minimumInteritemSpacing 屬性相對應, 他可以具體控制每個區段內的最小條目間距
collectionView:layout:referenceSizeForHeaderInSection: --- 與 headerReferenceSize 相對應
collectionView:layout:referenceSizeForHeaderInSection: --- 與 footerReferenceSize 相對應
3.4 內邊距: 最小行距及最小條目間距屬性定義了區段內的某個條目與其他條目之間的位置關系, 與之對應 sectionInset 屬性則描述了區段內所有條目的總體邊界與外圍的集合視圖的邊界
上代碼: 1.
采用流式布局的簡單集合視圖
1. 在 storyBoard 中創建一個 UICollectionViewController
2.
#import <UIKit/UIKit.h>
@interface ViewController : UICollectionViewController
@end
#import "ViewController.h"
@interface ViewController ()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, assign) BOOL useHeader;
@property (nonatomic, assign) BOOL useFooter;
@property (nonatomic, assign) NSInteger numberOfSectin;
@property (nonatomic, assign) NSInteger itemsInSection;
@end
@implementation ViewController
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
{
if (self = [super initWithCollectionViewLayout:layout]) {
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.useFooter = NO;
self.useHeader = NO;
self.numberOfSectin = 1;
self.itemsInSection = 10;
// 注冊 Cell
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
// 注冊 Header
[self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
// 注冊 Footer
[self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footer"];
self.collectionView.backgroundColor = [UIColor lightGrayColor];
// 允許用戶多選
self.collectionView.allowsMultipleSelection = YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return self.numberOfSectin;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.itemsInSection;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5f];
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
if (kind == UICollectionElementKindSectionHeader) {
UICollectionReusableView *header = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header" forIndexPath:indexPath];
header.backgroundColor = [UIColor blackColor];
return header;
} else if (kind == UICollectionElementKindSectionFooter) {
UICollectionReusableView *footer = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footer" forIndexPath:indexPath];
footer.backgroundColor = [UIColor darkGrayColor];
return footer;
}
return nil;
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Selected item at indexPath: %@", indexPath);
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Deselect item at indexPath: %@",indexPath);
}
#pragma mark - UICollectionViewDelegateFlowLayout
// header 大小
- (CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return self.useHeader ? CGSizeMake(60.0, 30.0) : CGSizeZero;
}
/// footer 大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
return self.useFooter ? CGSizeMake(60.0, 30.0) : CGSizeZero;
}
@end
<img src="http://upload-images.jianshu.io/upload_images/536129-62df25095adb23b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width = "320" height = "480" alt="圖片名稱" align=center />