UICollectionView与UITableView十分的类似,但是UICollection的功能更加的强大,其强大之处在与UICollectionViewLayout,这个类的作用是给cell提供布局信息。因此,不管什么样的布局,都可以通过UICollectionViewLayout实现。而本文主要说说UICollectionView的一些相关使用,如:cell的移动和cell的自定义编辑菜单,还有布局的变化(即CollectionView中Layout对象的改变)。
1.cell自定义编辑菜单
如上图所示,其中的copy、custom按钮对应的菜单就是cell自带的编辑菜单。当我们长按cell中对应的某个项目的时候,就会弹出编辑菜单。编辑菜单是一个UIMenuController单例,而其中的item对应的是UIMenuItem。要实现编辑菜单也十分的简单,只需要按照如下的步骤即可:
第一步:实现如下三个代理方法
/**
* 当我们长按cell的时候会调用此方法,告知是否可以显示编辑菜单
*/
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
/**
* 此方法需实现,告知我们需要显示的item(即是否显示copy、cut等item,在ios7之后是在cell中告知的)
*/
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
return NO;
}
/**
* 此方法也需实现,但实现类容可无
*/
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
}
第二步:定制自己的item,本步骤可有可无,全凭是否添加自定义item
// 实例化一个item
UIMenuItem * customItem = [[UIMenuItem alloc] initWithTitle:@"Custom" action:@selector(customItem:)];
// 添加到UIMenuController中去
[[UIMenuController sharedMenuController] setMenuItems:@[customItem]];
注意:此处的selector对应的方法必须放在自定义cell的实现文件中。
第三步:cell中添加必须的方法
// 此方法保证UIMenuController能称为第一响应者,能被显示出来
- (BOOL)canBecomeFirstResponder {
return YES;
}
// 这里决定要显示的item,在这里可以过滤到系统的item
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
// 显示系统自带的copy item
if (action == @selector(copy:)) {
return YES;
}
if (action == @selector(customItem:)) {
return YES;
}
return NO;
}
- (void)customItem:(UIMenuController *)sender {
}
- (void)copy:(UIMenuController *)sender {
}
至此,自定义cell的编辑菜单就算完成了,当然点击item需要执行的操作应该控制器在完成,这里在selector方法中应该通过代理回到控制器中去完成。
2.Layout Object 的改变
首先我们来看看效果
Layout Object对象的改变有两种方式,第一种是通过代码方式,这种方法简单,可以带有动画,但是不能实现动画的进度控制。第二种方式是一种基于手势的改变LayoutObject对象,这种方式对应的动画进度是可控的。但是,在改变LayoutObject对象之前,要确保你有两种布局方式,即两个Layout对象。
本文采用的是自定义的两个布局对象为:环形布局、堆叠式布局
首先看一看第一种方式:
- (IBAction)changeItem:(id)sender {
static BOOL isStack = NO;
isStack = !isStack;
if (isStack) {
[self.collectionView setCollectionViewLayout:self.stackLayout animated:YES];
} else {
[self.collectionView setCollectionViewLayout:self.cycleLayout animated:YES];
}
}
这里,当我点击按钮的时候,布局就动画的从cycleLayout布局变化到了stackLayout布局,在此点击就换回来了。第一种方法的实现就完成了。
接下来看看第二种方式:
看看动画
首先给UICollectionView添加一个屏幕边缘手势:
UIScreenEdgePanGestureRecognizer * edgePan = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgePan:)];
edgePan.edges = UIRectEdgeLeft;
[self.view addGestureRecognizer:edgePan];
其次实现手势响应方法
- (void)edgePan:(UIScreenEdgePanGestureRecognizer *)edgePan {
// 计算拖拽的进度
CGFloat progress = [edgePan translationInView:self.view].x / 200;
progress = MIN(1, MAX(0, progress));
if (edgePan.state == UIGestureRecognizerStateBegan) { // 是手势开始
// 告知collection开始交互变化Layout,以及交互变化layout完成调用的block
self.transitionLayout = [self.collectionView startInteractiveTransitionToCollectionViewLayout:self.stackLayout completion:^(BOOL completed, BOOL finished) {
NSLog(@"完成");
}];
} else if (edgePan.state == UIGestureRecognizerStateChanged) { // 如果是手势进行中,将进度告知collection的开始时返回的对象
self.transitionLayout.transitionProgress = progress;
[self.transitionLayout invalidateLayout];
} else if (edgePan.state == UIGestureRecognizerStateEnded) { // 如果是手势结束
if (progress < 0.5) { // 如果完成进度小于0.5,就取消本次layout对象的转变
[self.collectionView cancelInteractiveTransition];
} else {
[self.collectionView finishInteractiveTransition];
}
}
}
当我们的手指从屏幕边缘划过的时候,如果是刚开始滑动,就告知UICollectionView开始交互变化布局,调用UICollectInView的startInteractiveTransitionToCollectionViewLayout:方法,此方法会返回一个系统提供的用于变化布局的对象,将此对象保存下来。当滑动进行中,我们通过滑动的距离和规定的滑动距离向比较,得出滑动完成的进度,并把进度告知系统提供的用于变化布局的对象。当滑动完成时,通过滑动完成进度与0.5的比较告知是否完成交互变化布局。
3.cell的手势交互移动
不多说,还是先看看效果:
直接上代码:
第一步:给cell添加tap手势
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
KVCollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellReuseIdentifier forIndexPath:indexPath];
cell.indexLabel.text = [NSString stringWithFormat:@"我是国民好男人%ld", indexPath.row];
// 给cell添加手势
UIPanGestureRecognizer * panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[cell addGestureRecognizer:panGesture];
return cell;
}
第二步:实现手势响应方法
- (void)pan:(UIPanGestureRecognizer *)pan {
UIView * view = pan.view;
NSIndexPath * indexPath = [self.collectionView indexPathForCell:(KVCollectionViewCell *)view];
CGPoint curentPoint = [pan locationInView:self.collectionView];
if (pan.state == UIGestureRecognizerStateBegan) { // 手势开始
// 告知collectionView开始交互移动
[self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];
} else if (pan.state == UIGestureRecognizerStateChanged) { // 手势进行
// 告知collectionView更新交互移动
[self.collectionView updateInteractiveMovementTargetPosition:curentPoint];
} else if (pan.state == UIGestureRecognizerStateCancelled) { // 手势取消
// 告知collectionView交互移动结束
[self.collectionView cancelInteractiveMovement];
} else if (pan.state == UIGestureRecognizerStateEnded) { // 手势结束
// 告知collectionView交互移动结束
[self.collectionView endInteractiveMovement];
}
}
第三步:实现手势驱动必须的代理和数据源方法
/**
* 数据源方法,告知特定item是否可以移动
*/
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
/**
* 手势移动结束调动此方法,以最终确定目的地的IndexPath
*/
- (NSIndexPath *)collectionView:(UICollectionView *)collectionView targetIndexPathForMoveFromItemAtIndexPath:(NSIndexPath *)originalIndexPath toProposedIndexPath:(NSIndexPath *)proposedIndexPath {
// 通常直接返回proposedIndexPath即可
return proposedIndexPath;
}
/**
* 手势移动结束,系统调用此方法告知移动的对象开始的IndexPath、移动结束的IndexPath
*/
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
// 在此处改变数据源中对应indexPath
}
本文所使用的Demo一上传至github