IOS 6中开放了集合视图API——UICollectionView,方便了网格视图的开发。集合视图有4个重要组成部分:
1、单元格,它是集合视图中的一个单元格。
2、节,它是集合视图中的一个行数据,由多个单元格组成。
3、补充视图,它是节的头和脚。
4、装饰视图,集合视图中的背景视图。
UICollectionView继承自UIScrollView。与选择器类似,集合视图也有两个协议:UICollectionViewDelegate委托协议和UICollectionViewDataSource数据源协议。UICollectionViewCell是单元格类。集合视图的布局由UICollectionViewLayout类定义,它是一个抽象类。UICollectionViewFlowLayout是UICollectionViewLayout的子类。对于复杂的布局,可以自定义UICollectionViewLayout类。UICollectionView对应的控制器是UICollectionViewController类(具体如下图所示)。
下面是一个简单的sample。
// ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UICollectionViewController
@property (strong, nonatomic) NSArray * events;
@end
这里ViewController的父类是UICollectionViewController(需要实现集合视图的数据源和代理协议,这里我们直接在storyboard里操作),events是一个数组,用于存放数据。数据保存在plist文件中,组织方式如下所示,
我们在viewDidLoad方法中读取数据:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"events"ofType:@"plist"];
//获取文件中的数据
self.events = [[NSArray alloc] initWithContentsOfFile:plistPath];
}
接下来我们需要定义集合视图的单元格,我们可以在storyboard中设计也可以通过代码来设计。单元格是一个视图,可以在它的内部放置其它视图或控件。
首先我们定义一个单元格类,注意它的父类必须是UICollectionViewCell。因为数据单元中定义了key分别为image和name的两个键值对,所以在单元格类中显示数据就需要两个属性与之对应。这里我们定义一个UIImageView和一个UILabel属性(并定义成输出口)。
// Cell.h
#import <UIKit/UIKit.h>
@interface Cell : UICollectionViewCell
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UILabel *label;
@end
Cell.m代码如下:
#import "Cell.h"
@implementation Cell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
@end
因为我们的单元格视图在Interface Builder设计,所以 在这个文件中我们不需要做任何事情(上述代码是自动生成的)。如果我们通过代码实现单元格视图,就需要在initWithFrame:函数中实现UIImageView和UILabel两个控件的初始化代码。
接下来打开ViewController的storyboard文件,将界面的View Controller下的View换成Collection View(如下图)。
这里我们把结合视图的背景改成白色,选择Collection View并修改其背景色。然后选择Collection View Cell,打开其标识检查器,将Custom Class改成Cell(如下左图);打开其属性检查器,在Collection Reusable View的Identifier中输入Cell(如下右图),这是可重用单元格标识。
上面提到的“可重用单元格”是为了节约内存开销而设计的,当屏幕在翻动时,旧的单元格退出屏幕,新的单元格进入屏幕,如果每次都实例化单元格,必然增加内心开销,可重用单元格就是不去实例化新的单元格,而是先使用可重用单元格标识到视图中去找,找到了就使用,没有则创建。IOS 6对可重用单元格进行优化,不在需要做nil判断了。之前代码
Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
if(!cell){
...
}
改成如下形式
Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
说了这么多,我们的单元格视图还没设计呢。接下来从对象库中分别拖拽一个UIImageView和UILabel到单元格中。由于要往里面动态填数据,所以我们要将这两个控件与Cell类中的两个输出口连线。具体操作方法是:选中Cell,然后按住control将鼠标拖拽到UIImageView控件上,这时会弹出一个菜单,选择Image View。用同样的方法将Label控件与label属性连接起来。
下面直接看ViewController.m中实现的集合数据源和代理协议方法:
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return [self.events count] / 2;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 2;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
NSDictionary *event = [self.events objectAtIndex:(indexPath.section*2 + indexPath.row)];
cell.label.text = [event objectForKey:@"name"];
cell.imageView.image = [UIImage imageNamed:[event objectForKey:@"image"]];
return cell;
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *event = [self.events objectAtIndex:(indexPath.section*2 + indexPath.row)];
NSLog(@"select event name : %@", [event objectForKey:@"name"]);
}
UICollectionViewDataSource协议的定义如下:
@protocol UICollectionViewDataSource <NSObject>
@required
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
@optional
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
// The view that is returned must be retrieved from a call to -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
@end
这里的collectionView:numberOfItemsInsection:方法是定义节中的单元格数(列数),collectionView:cellForItemAtIndexPath:方法是定义单元格中的显示数据,这两个方法是必须要实现的。另两个方法,numberOfSectionsInCollectionView:方法是定义集合视图中节的数目,collectionView:viewForSupplementaryElementOfKind:atIndexPath:方法是定义某个节的补充视图显示数据的。
UICollectionViewDelegate协议定义如下:
@protocol UICollectionViewDelegate <UIScrollViewDelegate>
@optional
// Methods for notification of selection/deselection and highlight/unhighlight events.
// The sequence of calls leading to selection from a user touch is:
//
// (when the touch begins)
// 1. -collectionView:shouldHighlightItemAtIndexPath:
// 2. -collectionView:didHighlightItemAtIndexPath:
//
// (when the touch lifts)
// 3. -collectionView:shouldSelectItemAtIndexPath: or -collectionView:shouldDeselectItemAtIndexPath:
// 4. -collectionView:didSelectItemAtIndexPath: or -collectionView:didDeselectItemAtIndexPath:
// 5. -collectionView:didUnhighlightItemAtIndexPath:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath; // called when the user taps on an already-selected item in multi-select mode
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
// These methods provide support for copy/paste actions on cells.
// All three should be implemented if any are.
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
@end
这里提供的方法比较多,使用方便,都是可选实现的。我们代码中只实现了collectionView:didSelectItemAtIndexPath:方法,它在选择单元格之后触发。其他的代理方法文档上都有很清楚的描述,不一一赘述。