前言
本文的所有代码,是基于上一篇UICollectionView(1)--基本实现的代码上编写的,目的是为了代码的连贯性。在文章系列的结束,会给出代码Demo。
上一节,我们基本实现了一个纵向的UICollectionView,但是所有的Cell都是定住无法移动的。当然我们也可以通过数据源的改动然后[self.mainCollectionView reloadData]
来更新UI,甚至只更新局部Cell。
但是更体现交互性质的是,用户可以直接在UI上移动Cell,而你要做的是更改对应数据在数据源中的位置。
我们使用的是长按手势开启UICollectionView,通过长按手势在UICollectionView上的移动位置,来移动Cell,同时不断的更换选中的Cell在数据源中的位置。在长按手势结束或者被打断等状况下结束移动。原理说清楚了,下面直接上代码。
1.为UICollectionView添加长按手势。
-(UICollectionView *)mainCollectionView
{
if (!_mainCollectionView) {
UICollectionViewFlowLayout * layout = [UICollectionViewFlowLayout new];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.sectionHeadersPinToVisibleBounds = NO;//头部视图悬停设为YES
layout.sectionFootersPinToVisibleBounds = NO;//尾部视图悬停设为YES
_mainCollectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
_mainCollectionView.backgroundColor = [UIColor whiteColor];
_mainCollectionView.delegate = self;
_mainCollectionView.dataSource = self;
[_mainCollectionView registerClass:[MainCollectionViewCell class] forCellWithReuseIdentifier:[MainCollectionViewCell reuseIdentifier]];
[_mainCollectionView registerClass:[MainCollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[MainCollectionHeaderView reuseIdentifier]];
[_mainCollectionView registerClass:[MainCollectionFooterView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[MainCollectionFooterView reuseIdentifier]];
//添加长按手势来移动 item
UILongPressGestureRecognizer * longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didReceiveLongPress:)];
[_mainCollectionView addGestureRecognizer:longPress];
_mainCollectionView.userInteractionEnabled = YES;
}
return _mainCollectionView;
}
2.在长按手势对应的方法中开启和移动Cell。
#pragma mark - private methods
//长按手势状态控制移动 item
-(void)didReceiveLongPress:(UILongPressGestureRecognizer *)longpress
{
if (self.mainCollectionView == nil) return;
switch (longpress.state) {
case UIGestureRecognizerStateBegan:
{
//搜寻长按手势在UICollectionView上的位置,若不为nil则开启移动对应位置Cell
NSIndexPath * indexpath = [self.mainCollectionView indexPathForItemAtPoint:[longpress locationInView:self.mainCollectionView]];
if (indexpath == nil) {
break;
}
[self.mainCollectionView beginInteractiveMovementForItemAtIndexPath:indexpath];
}
break;
case UIGestureRecognizerStateChanged:
{
//长按后移动的情况下,不断通过手势对应的位置更新被选中的Cell的位置
[self.mainCollectionView updateInteractiveMovementTargetPosition:[longpress locationInView:self.mainCollectionView]];
}
break;
case UIGestureRecognizerStateEnded:
{
//长按手势结束,关闭移动
[self.mainCollectionView endInteractiveMovement];
}
break;
default:
{
//其它情况,关闭移动
[self.mainCollectionView endInteractiveMovement];
}
break;
}
}
3.在UICollectionViewDataSource方法里面有一个- (BOOL)collectionView:canMoveItemAtIndexPath:NS_AVAILABLE_IOS(9_0);
的方法,通过返回一个BOOL值来验证是否可以移动Cell。
//是否允许移动 item
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0)
{
return YES;
}
4.UI完成后也要更新对应数据在数据源中的位置,系统提供一个- (void)collectionView:moveItemAtIndexPath:toIndexPath: NS_AVAILABLE_IOS(9_0);
的回调方法。
//移动 item 行为
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
{
NSMutableArray * moveArray = [NSMutableArray arrayWithArray:self.dataArray];
MainModel * model = moveArray[sourceIndexPath.section] [sourceIndexPath.item];
NSMutableArray * subArray = [NSMutableArray arrayWithArray:moveArray[sourceIndexPath.section]];
[subArray removeObject:model];
moveArray[sourceIndexPath.section] = [NSArray arrayWithArray:subArray];
NSMutableArray * subToArray = [NSMutableArray arrayWithArray:moveArray[destinationIndexPath.section]];
[subToArray insertObject:model atIndex:destinationIndexPath.item];
moveArray[destinationIndexPath.section] = [NSArray arrayWithArray:subToArray];
self.dataArray = [NSArray arrayWithArray:moveArray];
}
5.效果图。
有朋友问这是iOS9.0之后的方法,那iOS9.0之前呢,解决方法如下:
1.原理:我们在长按的开始,获取长按的Cell,将其复制一份,并将原来的Cell隐藏,让复制的View跟着我们的移动而移动,并且在移动的过程中不断将真实的隐藏了的Cell跟我们移动所到位置的Cell做交换,千万记住,数据源中的交换必须发生在UI上的交换之前。
2.代码:
//长按手势状态控制移动 item
-(void)didReceiveLongPress:(UILongPressGestureRecognizer *)longpress
{
if (self.mainCollectionView == nil) return;
CGPoint location = [longpress locationInView:self.mainCollectionView];
NSIndexPath * indexpath = [self.mainCollectionView indexPathForItemAtPoint:location];
static UICollectionViewCell * cell;
static UIView * fakeCell;
static CGPoint beginLocation;
static NSIndexPath * beginIndex;
switch (longpress.state) {
case UIGestureRecognizerStateBegan:
{
cell = [self.mainCollectionView cellForItemAtIndexPath:indexpath];
cell.hidden = YES;
fakeCell = [self customSnapshoFromView:cell.contentView];
fakeCell.frame = cell.frame;
[self.mainCollectionView addSubview:fakeCell];
beginLocation = location;
beginIndex = indexpath;
}
break;
case UIGestureRecognizerStateChanged:
{
CGFloat moveX = location.x-beginLocation.x;
CGFloat moveY = location.y-beginLocation.y;
fakeCell.center = CGPointMake(fakeCell.center.x+moveX, fakeCell.center.y+moveY);
beginLocation = location;
NSIndexPath * nowIndexpath = [self.mainCollectionView indexPathForItemAtPoint:location];
UIView * nowView = [self.mainCollectionView cellForItemAtIndexPath:nowIndexpath];
if ((nowIndexpath.section==0&&nowIndexpath.row==0)&&(nowView==nil)) {
nowIndexpath = [NSIndexPath indexPathForItem:[self.mainCollectionView numberOfItemsInSection:beginIndex.section]-1 inSection:beginIndex.section];
}
[self changeDataSourcesFromSources:beginIndex toDestination:nowIndexpath];
[self.mainCollectionView moveItemAtIndexPath:beginIndex toIndexPath:nowIndexpath];
beginIndex = nowIndexpath;
}
break;
case UIGestureRecognizerStateEnded:
{
[fakeCell removeFromSuperview];
fakeCell = nil;
cell.hidden = NO;
}
break;
default:
{
[fakeCell removeFromSuperview];
fakeCell = nil;
cell.hidden = NO;
}
break;
}
}
-(void)changeDataSourcesFromSources:(NSIndexPath *)sourceIndexPath toDestination:(NSIndexPath *)destinationIndexPath
{
NSMutableArray * moveArray = [NSMutableArray arrayWithArray:self.dataArray];
MainModel * model = moveArray[sourceIndexPath.section][sourceIndexPath.item];
NSMutableArray * subArray = [NSMutableArray arrayWithArray:moveArray[sourceIndexPath.section]];
[subArray removeObject:model];
moveArray[sourceIndexPath.section] = [NSArray arrayWithArray:subArray];
NSMutableArray * subToArray = [NSMutableArray arrayWithArray:moveArray[destinationIndexPath.section]];
[subToArray insertObject:model atIndex:destinationIndexPath.item];
moveArray[destinationIndexPath.section] = [NSArray arrayWithArray:subToArray];
self.dataArray = [NSArray arrayWithArray:moveArray];
}
#pragma mark 创建cell的快照
- (UIView *)customSnapshoFromView:(UIView *)inputView {
// 用cell的图层生成UIImage,方便一会显示
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, NO, 0);
[inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 自定义这个快照的样子(下面的一些参数可以自己随意设置)
UIView *snapshot = [[UIImageView alloc] initWithImage:image];
// snapshot.layer.masksToBounds = NO;
// snapshot.layer.cornerRadius = 0.0;
// snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
// snapshot.layer.shadowRadius = 5.0;
// snapshot.layer.shadowOpacity = 0.4;
return snapshot;
}