简介
UICollectionView 和 UICollectionViewController 类是iOS6 新引进的API,用于展示集合视图,布局更加灵活,可实现多列布局,用法类似于UITableView 和 UITableViewController 类。
使用UICollectionView 必须实现UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout这三个协议。
UICollectionViewFlowLayout
UICollectionViewFlowLayout是苹果内置的一种流式的布局,其继承于UICollectionViewLayout,负责整个视图的布局,通过这个类,可以高度的定制每个item的位置,以及表头、脚注和装饰视图的位置,只需简单的配置几个属性就可以完成整个布局的搭建。
初始化方法
alloc + init
常用属性
itemSize:设置item尺寸
minimumLineSpacing:设置每行之间的间距
minimumInteritemSpacing:设置每行内部item之间的间距
headerReferenceSize:设置头部size
footerReferenceSize:设置脚注size
scrollDirection:设置滚动方向
sectionHeadersPinToVisibleBounds:设置是否当元素超出屏幕之后固定头部视图位置,默认NO
sectionFootersPinToVisibleBounds:设置是否当元素超出屏幕之后固定尾部视图位置,默认NO
属性配置UICollectionViewFlowLayout是在全局配置布局,想要单独定制每个item元素,需遵守UICollectionViewDelegate以及UICollectionViewDelegateFlowLayout协议,并根据需要实现如下方法即可:
// 1、单独定制item的尺寸
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
// 2、定义每个UICollectionView的margin(间距),对每一个section单独设置边界,即内部cell上下左右距离header和footer的边界(间距)
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
// 3、单独定制每行之间的间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
// 4、单独定制每行item之间的间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
// 5、单独定制头部视图size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
// 6、单独定制脚注视图size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
UICollectionView
初始化
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
常用属性
delegate:设置代理
dataSource:设置数据源
allowsSelection:设置是否可以选中
allowsMultipleSelection:设置是否可以多选
注册
- 注册Cell
- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- 注册头部或尾部视图
// @param elementKind
// UICollectionElementKindSectionHeader:头部
// UICollectionElementKindSectionFooter:尾部
- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
重用
// 1、Cell重用
- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
// 2、supplementary View重用
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
操作
- items操作
// 插入
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths;
// 删除
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths;
// 刷新
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths;
// 移动
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
- sections操作
// 插入
- (void)insertSections:(NSIndexSet *)sections;
// 删除
- (void)deleteSections:(NSIndexSet *)sections;
// 刷新
- (void)reloadSections:(NSIndexSet *)sections;
// 移动
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
indexPath 获取方法
// 根据某一个点的位置获取item的indexPath
- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point;
// 根据cell获取item的indexPath
- (nullable NSIndexPath *)indexPathForCell:(UICollectionViewCell *)cell;
reordering 重新排序
- 一般重新排序步骤需经历如下周期,与手势搭配使用,根据手势的周期变化执行不同的方法:
// 1、设置是否允许移动item
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath;
// 2、一般在手势开始时调用,与 indexPathForItemAtPoint 搭配使用,根据手势在视图上的位置获取item所在的indexPath, 开始在特定的索引路径上对cell(单元)进行Interactive Movement(交互式移动工作)
- (BOOL)beginInteractiveMovementForItemAtIndexPath:(NSIndexPath *)indexPath;
// 3、 在手势作用期间更新交互移动的目标位置。
- (void)updateInteractiveMovementTargetPosition:(CGPoint)targetPosition;
// 4、在完成手势动作后,结束交互式移动
- (void)endInteractiveMovement;
// 5、取消移动
- (void)cancelInteractiveMovement;
// 6、当移动结束后,如下方法将会被触发。
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;
UIScrollViewDelegate
// 1、设置是否允许选中
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
// 2、设置是否允许取消选中
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
// 3、选中
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
// 4、取消选中
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
UICollectionViewDataSource
// 1、设置section组数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
// 2、设置item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
// 3、配置cell
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
// 4、自定义头部、脚注视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
tips:配置Cell以及头部或者脚注视图需子类化
UICollectionViewCell
或UICollectionReusableView
,并重写initWithFrame
方法,添加视图;
案例
效果展示
代码示例
自定义 cell 代码示例
#import <UIKit/UIKit.h>
@interface CustomCollectionViewCell : UICollectionViewCell
@property (nonatomic, strong) UILabel *displayLabel; /**< 展示视图 */
@end
#import "CustomCollectionViewCell.h"
@implementation CustomCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.layer.borderWidth = 1.0f;
self.layer.borderColor = [UIColor blackColor].CGColor;
self.layer.masksToBounds = YES;
[self.contentView addSubview:self.displayLabel];
}
return self;
}
#pragma mark *** Getters ***
- (UILabel *)displayLabel {
if (!_displayLabel) {
_displayLabel = [[UILabel alloc] initWithFrame:self.bounds];
_displayLabel.textAlignment = NSTextAlignmentCenter;
}
return _displayLabel;
}
@end
自定义 ReusableView(头部、尾部) 代码示例
#import <UIKit/UIKit.h>
// 自定义头部、尾部视图
@interface CustomCollectionReusableView : UICollectionReusableView
@property (nonatomic, strong) UILabel *displayLabel; /**< 展示标签 */
@end
#import "CustomCollectionReusableView.h"
@implementation CustomCollectionReusableView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self addSubview:self.displayLabel];
}
return self;
}
#pragma mark *** Getters ***
- (UILabel *)displayLabel {
if (!_displayLabel) {
_displayLabel = [[UILabel alloc] initWithFrame:self.bounds];
_displayLabel.backgroundColor = [UIColor redColor];
_displayLabel.textColor = [UIColor whiteColor];
_displayLabel.textAlignment = NSTextAlignmentCenter;
}
return _displayLabel;
}
@end
viewController 代码示例
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
#import "ViewController.h"
#import "CustomCollectionViewCell.h"
#import "CustomCollectionReusableView.h"
enum NSInteger {
InsertItemTag = 100,
DeleteItemTag
};
static NSString *const kTitle = @"CollectionView";
static NSString *const kCollectionViewCellIdentifier = @"collectionViewCellIdentifier";
static NSString *const kCollectionElementKindSectionHeaderIdentifier = @"headerIdentifier";
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout> {
BOOL _selected;
NSMutableArray *_dataSource; /**< 数据源 */
NSMutableArray *_selectedDataArr; /**< 选中数据源 */
}
@property (nonatomic, strong) UICollectionView *collectionView; /**< 集合视图 */
@property (nonatomic, strong) UICollectionViewFlowLayout *collectionViewFlowLayout; /**< 流式布局 */
- (void)initializeDataSource; /**< 初始化数据源 */
- (void)initializeUserInterface; /**< 初始化用户界面 */
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initializeDataSource];
[self initializeUserInterface];
}
#pragma mark *** Initialize ***
- (void)initializeDataSource {
_selectedDataArr = [NSMutableArray array];
// 添加数据
_dataSource = [NSMutableArray array];
for (int i = 0; i < 20; i++) {
[_dataSource addObject:[NSString stringWithFormat:@"%d", i]];
}
}
- (void)initializeUserInterface {
self.title = kTitle;
self.automaticallyAdjustsScrollViewInsets = NO;
// 视图加载
[self.view addSubview:self.collectionView];
// 配置导航栏
// 插入
UIBarButtonItem *insertItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:@selector(respondsToBarButtonItem:)];
insertItem.tag = InsertItemTag;
self.navigationItem.rightBarButtonItem = insertItem;
// 删除
UIBarButtonItem *deleteItem = [[UIBarButtonItem alloc] initWithTitle:@"删除"
style:UIBarButtonItemStylePlain
target:self
action:@selector(respondsToBarButtonItem:)];
deleteItem.tag = DeleteItemTag;
self.navigationItem.leftBarButtonItem = deleteItem;
// 添加长按手势,重排item
UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(respondsToGesture:)];
[self.collectionView addGestureRecognizer:longGesture];
}
#pragma mark *** Gestures ***
- (void)respondsToGesture:(UIGestureRecognizer *)gesture {
NSLog(@"%@", NSStringFromSelector(_cmd));
// 监听手势状态
switch (gesture.state) {
// 1、手势开始
case UIGestureRecognizerStateBegan: {
// 获取手势长按位置
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:[gesture locationInView:self.collectionView]];
// 开始在特定的索引路径上对cell(单元)进行Interactive Movement(交互式移动工作)
[self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];
}
break;
// 2、手势变换
case UIGestureRecognizerStateChanged: {
// 在手势作用期间更新交互移动的目标位置。
[self.collectionView updateInteractiveMovementTargetPosition:[gesture locationInView:self.collectionView]];
}
break;
// 3、手势结束
case UIGestureRecognizerStateEnded: {
// 在完成手势动作后,结束交互式移动
[self.collectionView endInteractiveMovement];
}
break;
default: {
// 4、默认状态下,取消Interactive Movement。
[self.collectionView cancelInteractiveMovement];
}
break;
}
}
#pragma mark *** Events ***
- (void)respondsToBarButtonItem:(UIBarButtonItem *)sender {
switch (sender.tag) {
case InsertItemTag: {
// 弹出框
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"请输入您要添加的数字:" preferredStyle:UIAlertControllerStyleAlert];
[alertController.view layoutIfNeeded];
// 添加文本输入框
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.textAlignment = NSTextAlignmentCenter;
}];
// 添加按钮
[alertController addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:nil]];
[alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
// 获取文本输入框
UITextField *textField = alertController.textFields[0];
// 获取文本
NSString *context = textField.text;
// 异常处理
if (context.length == 0) {
NSLog(@"请输入文本!");
return ;
}
// 数据源插入
[_dataSource insertObject:context atIndex:0];
// 单元格插入
[_collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
break;
case DeleteItemTag: {
if ([self.navigationItem.leftBarButtonItem.title isEqualToString:@"点击删除"]) {
NSLog(@"执行删除逻辑!");
// 遍历删除数据源数据
for (NSString *data in _selectedDataArr) {
[_dataSource removeObject:data];
}
// 刷新集合视图
[self.collectionView reloadData];
self.collectionView.allowsSelection = NO;
}
// 切换编辑状态
_selected = !_selected;
self.navigationItem.leftBarButtonItem.title = _selected ? @"选择删除项" : @"删除";
if ([self.navigationItem.leftBarButtonItem.title isEqualToString:@"选择删除项"]) {
self.collectionView.allowsSelection = YES;
}
}
break;
default:
break;
}
}
#pragma mark *** UICollectionViewDataSource ***
// 设置组数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
// 设置行数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _dataSource.count;
}
// 设置单元格
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// item重用机制
CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCollectionViewCellIdentifier forIndexPath:indexPath];
cell.displayLabel.backgroundColor = [UIColor whiteColor];
cell.displayLabel.text = _dataSource[indexPath.row];
return cell;
}
// 设置自定义头部、尾部视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
// UICollectionElementKindSectionHeader:头部视图
// UICollectionElementKindSectionFooter:尾部视图
// 如果同时自定义头部、尾部视图,可根据 kind 参数判断当前加载的是头部还是尾部视图,然后进行相应配置。
// 重用机制
CustomCollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kCollectionElementKindSectionHeaderIdentifier forIndexPath:indexPath];
headerView.displayLabel.text = @"[自定义头部视图]";
return headerView;
}
// 设置是否允许移动
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
// 移动元素
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
#pragma mark *** UICollectionViewDelegateFlowLayout ***
// 通过UICollectionViewDelegateFlowLayout提供的方法可高度定制流式布局;
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 10, 0, 10);
}
#pragma mark *** UICollectionViewDelegate ***
// 选中单元格
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
self.navigationItem.leftBarButtonItem.title = @"点击删除";
_selected = YES;
CustomCollectionViewCell *cell = (CustomCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
cell.displayLabel.backgroundColor = [UIColor greenColor];
[_selectedDataArr addObject:cell.displayLabel.text];
}
// 取消选中
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
CustomCollectionViewCell *cell = (CustomCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
cell.displayLabel.backgroundColor = [UIColor whiteColor];
[_selectedDataArr removeObject:cell.displayLabel.text];
}
#pragma mark *** Getters ***
- (UICollectionViewFlowLayout *)collectionViewFlowLayout {
if (!_collectionViewFlowLayout) {
_collectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
// 设置item估计值
_collectionViewFlowLayout.estimatedItemSize = CGSizeMake(100, 100);
// 全局配置item尺寸,单独定义调用协议方法[sizeForItemAtIndexPath]
_collectionViewFlowLayout.itemSize = CGSizeMake(100, 100);
// 全局配置每行之间的间距,单独定义可调用协议方法[minimumLineSpacingForSectionAtIndex]
_collectionViewFlowLayout.minimumLineSpacing = 10;
// 全局配置每行内部item的间距,单独定义可调用协议方法[minimumInteritemSpacingForSectionAtIndex]
_collectionViewFlowLayout.minimumInteritemSpacing = 10;
// 设置头部size
_collectionViewFlowLayout.headerReferenceSize = CGSizeMake(CGRectGetWidth(self.view.bounds), 44);
// 设置尾部size
_collectionViewFlowLayout.footerReferenceSize = CGSizeZero;
// 设置滚动方向
// UICollectionViewScrollDirectionVertical
// UICollectionViewScrollDirectionHorizontal
_collectionViewFlowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
// 设置是否当元素超出屏幕之后固定头部视图位置,默认NO;
_collectionViewFlowLayout.sectionHeadersPinToVisibleBounds = YES;
// 设置是否当元素超出屏幕之后固定尾部视图位置,默认NO;
_collectionViewFlowLayout.sectionFootersPinToVisibleBounds = YES;
}
return _collectionViewFlowLayout;
}
- (UICollectionView *)collectionView {
if (!_collectionView) {
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds) - 64) collectionViewLayout:self.collectionViewFlowLayout];
_collectionView.backgroundColor = [UIColor brownColor];
// 设置是否允许滚动
_collectionView.scrollEnabled = YES;
// 设置是否允许选中,默认YES
_collectionView.allowsSelection = NO;
// 设置是否允许多选,默认NO
_collectionView.allowsMultipleSelection = YES;
// 设置代理
_collectionView.delegate = self;
// 设置数据源
_collectionView.dataSource = self;
// 注册Item
[_collectionView registerClass:[CustomCollectionViewCell class] forCellWithReuseIdentifier:kCollectionViewCellIdentifier];
// 注册头部视图
[_collectionView registerClass:[CustomCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kCollectionElementKindSectionHeaderIdentifier];
}
return _collectionView;
}
@end