修复了拖拽滚动时抖动的一个bug,新增编辑模式,进入编辑模式后不用长按触发手势,且在开启抖动的情况下会自动进入抖动模式,如图:

test.gif

图1:垂直滚动

drag1.gif

图2:水平滚动

drag2.gif

图3:配合瀑布流(我直接使用了上个项目的瀑布流模块做了集成实验)

drag5.gif

我将整个控件进行了封装,名字是XWDragCellCollectionView使用起来非常方便,github地址:可拖拽重排的CollectionView;使用也非常简单,只需3步,步骤如下:

1、继承于XWDragCellCollectionView;

2、实现必须实现的DataSouce代理方法:(在该方法中返回整个CollectionView的数据数组用于重排)
- (NSArray *)dataSourceArrayOfCollectionView:(XWDragCellCollectionView *)collectionView; 3、实现必须实现的一个Delegate代理方法:(在该方法中将重拍好的新数据源设为当前数据源)(例如 :_data = newDataArray)
- (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView newDataArrayAfterMove:(NSArray *)newDataArray;

详细的使用可以查看代码中的demo,支持设置长按事件,是否开启边缘滑动,抖动、以及设置抖动等级,这些在h文件里面都有详细说明,有需要的可以尝试一下,并多多提意见,作为新手,肯定还有很多不足的地方;

原理

在刚刚考虑这个效果的时候,我仔细分析了一下效果,我首先想到的就是利用截图大法,将手指要移动的cell截个图来进行移动,并隐藏该cell,然后在合适的时候交换cell的位置,造成是拖拽cell被拖拽到新位置的效果,我将主要实现的步骤分为如下步骤:

1、给CollectionView添加一个长按手势,用于效果驱动

- (void)xwp_addGesture{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(xwp_longPressed:)];
_longPressGesture = longPress;
//设置长按时间
longPress.minimumPressDuration = _minimumPressDuration;
[self addGestureRecognizer:longPress];
}

2、在手势开始的时候,得到手指所在的cell,并截图,并将原有cell隐藏

- (void)xwp_gestureBegan:(UILongPressGestureRecognizer *)longPressGesture{
//获取手指所在的cell
_originalIndexPath = [self indexPathForItemAtPoint:[longPressGesture locationOfTouch:0 inView:longPressGesture.view]];
UICollectionViewCell *cell = [self cellForItemAtIndexPath:_originalIndexPath];
//截图大法,得到cell的截图视图
UIView *tempMoveCell = [cell snapshotViewAfterScreenUpdates:NO];
_tempMoveCell = tempMoveCell;
_tempMoveCell.frame = cell.frame;
[self addSubview:_tempMoveCell];
//隐藏cell
cell.hidden = YES;
//记录当前手指位置
_lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view];
}

3、在手势移动的时候,计算出手势移动的距离,并移动截图视图,当截图视图于某一个cell(可见cell)相交到一定程度的时候,我就让调用系统的api交换这个cell和隐藏cell的位置,形成动画,同时更新数据源(更新数据源是最重要的操作!)

- (void)xwp_gestureChange:(UILongPressGestureRecognizer *)longPressGesture{
//计算移动距离
CGFloat tranX = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].x - _lastPoint.x;
CGFloat tranY = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].y - _lastPoint.y;
//设置截图视图位置
_tempMoveCell.center = CGPointApplyAffineTransform(_tempMoveCell.center, CGAffineTransformMakeTranslation(tranX, tranY));
_lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view];
//计算截图视图和哪个cell相交
for (UICollectionViewCell *cell in [self visibleCells]) {
//剔除隐藏的cell
if ([self indexPathForCell:cell] == _originalIndexPath) {
continue;
}
//计算中心距
CGFloat space = sqrtf(pow(_tempMoveCell.center.x - cell.center.x, 2) + powf(_tempMoveCell.center.y - cell.center.y, 2));
//如果相交一半就移动
if (space <= _tempMoveCell.bounds.size.width / 2) {
_moveIndexPath = [self indexPathForCell:cell];
//更新数据源(移动前必须更新数据源)
[self xwp_updateDataSource];
//移动
[self moveItemAtIndexPath:_originalIndexPath toIndexPath:_moveIndexPath];
//通知代理
//设置移动后的起始indexPath
_originalIndexPath = _moveIndexPath;
break;
}
}
}
/**
* 更新数据源
*/
- (void)xwp_updateDataSource{
NSMutableArray *temp = @[].mutableCopy;
//通过代理获取数据源,该代理方法必须实现
if ([self.dataSource respondsToSelector:@selector(dataSourceArrayOfCollectionView:)]) {
[temp addObjectsFromArray:[self.dataSource dataSourceArrayOfCollectionView:self]];
}
//判断数据源是单个数组还是数组套数组的多section形式,YES表示数组套数组
BOOL dataTypeCheck = ([self numberOfSections] != 1 || ([self numberOfSections] == 1 && [temp[0] isKindOfClass:[NSArray class]]));
//先将数据源的数组都变为可变数据方便操作
if (dataTypeCheck) {
for (int i = 0; i < temp.count; i ++) {
[temp replaceObjectAtIndex:i withObject:[temp[i] mutableCopy]];
}
}
if (_moveIndexPath.section == _originalIndexPath.section) {
//在同一个section中移动或者只有一个section的情况(原理就是将原位置和新位置之间的cell向前或者向后平移)
NSMutableArray *orignalSection = dataTypeCheck ? temp[_originalIndexPath.section] : temp;
if (_moveIndexPath.item > _originalIndexPath.item) {
for (NSUInteger i = _originalIndexPath.item; i < _moveIndexPath.item ; i ++) {
[orignalSection exchangeObjectAtIndex:i withObjectAtIndex:i + 1];
}
}else{
for (NSUInteger i = _originalIndexPath.item; i > _moveIndexPath.item ; i --) {
[orignalSection exchangeObjectAtIndex:i withObjectAtIndex:i - 1];
}
}
}else{
//在不同section之间移动的情况(原理是删除原位置所在section的cell并插入到新位置所在的section中)
NSMutableArray *orignalSection = temp[_originalIndexPath.section];
NSMutableArray *currentSection = temp[_moveIndexPath.section];
[currentSection insertObject:orignalSection[_originalIndexPath.item] atIndex:_moveIndexPath.item];
[orignalSection removeObject:orignalSection[_originalIndexPath.item]];
}
//将重排好的数据传递给外部,在外部设置新的数据源,该代理方法必须实现
if ([self.delegate respondsToSelector:@selector(dragCellCollectionView:newDataArrayAfterMove:)]) {
[self.delegate dragCellCollectionView:self newDataArrayAfterMove:temp.copy];
}
}

4、手势结束的时候将截图视图动画移动到隐藏cell所在位置,并显示隐藏cell并移除截图视图;

- (void)xwp_gestureEndOrCancle:(UILongPressGestureRecognizer *)longPressGesture{
UICollectionViewCell *cell = [self cellForItemAtIndexPath:_originalIndexPath];
//结束动画过程中停止交互,防止出问题
self.userInteractionEnabled = NO;
//给截图视图一个动画移动到隐藏cell的新位置
[UIView animateWithDuration:0.25 animations:^{
_tempMoveCell.center = cell.center;
} completion:^(BOOL finished) {
//移除截图视图、显示隐藏cell并开启交互
[_tempMoveCell removeFromSuperview];
cell.hidden = NO;
self.userInteractionEnabled = YES;
}];
}

关键效果的代码就是上面这些了,还有写细节的东西请大家自行查看源代码

写在最后

从iOS9开始,系统已经提供了重排的API,不用我们这么辛苦的自己写,不过想要只适配iOS9,还有一段时间,不过大家可以尝试去实现以下这几个API:

// Support for reordering
- (BOOL)beginInteractiveMovementForItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0); // returns NO if reordering was prevented from beginning - otherwise YES
- (void)updateInteractiveMovementTargetPosition:(CGPoint)targetPosition NS_AVAILABLE_IOS(9_0);
- (void)endInteractiveMovement NS_AVAILABLE_IOS(9_0);
- (void)cancelInteractiveMovement NS_AVAILABLE_IOS(9_0);

接下来,还准备研究一下CollectionView的转场和自定义布局,已经写了一些自定义布局效果了,总结好了再贴出来,CollectionView实在是一枚非常强大的控件,大家都应该去深入的研究一下,说不定会产生许多奇妙的想法!加油咯!最后复习一下github地址:可拖拽重排的CollectionView,如果觉得有帮助,请给与一颗star鼓励一下,谢谢!

文/wazrx(简书作者)
原文链接:http://www.jianshu.com/p/8f0153ce17f9
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

iOS之可拖拽重排的CollectionView的更多相关文章

  1. 可拖拽重排的CollectionView

    来源:wazrx 链接:http://www.jianshu.com/p/8f0153ce17f9 写在前面 这段时间都在忙新项目的事儿,没有时间倒腾,这两天闲下来,想着一直没有细细的研究Collec ...

  2. iOS开发之--storyboary下,拖拽一个tableview/collectionView/view 等,顶端下沉64个像素的处理方法

    大家可能会发现,在sb或者xib里面拖拽一个tableview/collectionview/view的,顶端会自动下沉64个像素,也就是说,运行在模拟器上去,自导航下面又自动下沉了64个像素, 那是 ...

  3. iOS-UICollectionView快速构造/拖拽重排/轮播实现

    代码地址如下:http://www.demodashi.com/demo/11366.html 目录 UICollectionView的定义 UICollectionView快速构建GridView网 ...

  4. iOS之XIB拖拽scrollView

    在使用storyboard和xib时,我们经常要用到ScrollView,还有自动布局AutoLayout,但是ScrollView和AutoLayout 结合使用,相对来说有点复杂.根据实践,我说一 ...

  5. 【Swift 4.0】iOS 11 UICollectionView 长按拖拽删除崩溃的问题

    正文 功能 用 UICollectionView 实现两个 cell 之间的位置交互或者拖拽某个位置删除 问题 iOS 11 以上拖拽删除会崩溃,在 iOS 9.10 都没有问题 错误 017-10- ...

  6. Swift2.0下UICollectionViews拖拽效果的实现

    文/过客又见过客(简书作者)原文链接:http://www.jianshu.com/p/569c65b12c8b著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 原文UICollecti ...

  7. 【UWP】拖拽列表项的排序功能实现

    在一些允许用户自定义栏目顺序的app(如:凤凰新闻.网易云音乐等),我们可以方便地拖拽列表项来完成列表的重新排序,进而完成对栏目顺序的重排.这个功能很人性化,而实现起来其实很简单(甚至都不用写什么后台 ...

  8. iOS开发拓展篇—xib中关于拖拽手势的潜在错误

    iOS开发拓展篇—xib中关于拖拽手势的潜在错误 一.错误说明 自定义一个用来封装工具条的类 搭建xib,并添加一个拖拽的手势. 主控制器的代码:加载工具条 封装工具条以及手势拖拽的监听事件 此时运行 ...

  9. ios 为什么拖拽的控件为weak 手写的strong

    ib拖拽的控件自动声明为weak  而平时自己手写的为strong 在ios中,对象默认都是强引用,不是强引用赋值后会立即释放 ib声明weak 不立即被释放 简单说就是 1.声明的弱引用指向强引用 ...

随机推荐

  1. Junit3.8 私有方法测试

    1. 测试类的私有方法时可以采取两种方式:1) 修改方法的访问修饰符,将private修改为default或public(但不推荐采取这种方式).2) 使用反射在测试类中调用目标类的私有方法(推荐). ...

  2. Java缓存学习之一:缓存

    一.缓存 1.什么是缓存? 缓存是硬件,是CPU中的组件,CPU存取数据的速度非常的快,一秒钟能够存取.处理十亿条指令和数据(术语:CPU主频1G),而内存就慢很多,快的内存能够达到几十兆就不错了,可 ...

  3. 启动程序的同时传参给接收程序(XE8+WIN764)

    相关资料: http://blog.csdn.net/yanjiaye520/article/details/7590252 注意事项: 1.ParamStr(0)是实例自己. 2.传的参数是以空格分 ...

  4. HDU 5832 A water problem (水题,大数)

    题意:给定一个大数,问你取模73 和 137是不是都是0. 析:没什么可说的,先用char 存储下来,再一位一位的算就好了. 代码如下: #pragma comment(linker, "/ ...

  5. String 和 byte[]

    使用默认字符集合 Encodes this String into a sequence of bytes using the platform's default charset, storing ...

  6. HtmlAgilityPack相关网页

    //多线程 http://www.cnblogs.com/jiangming/archive/2012/09/11/MultiThreadCallWebbrowser.html //替换Webbrow ...

  7. java堆栈

    一.堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存 ...

  8. 百度地图 >> 自定义控件

    前言 百度地图API中预定义的UI控件,比如NavigationControl平移缩放控件,CopyrightControl版权控件,MapTypeControl地图类型控件....,这些都继承自抽象 ...

  9. 关于Linux vi命令 vi命令一览表

    vi是所有UNIX系统都会提供的屏幕编辑器,它提供了一个视窗设备,通过它可以编辑文件.当然,对UNIX系统略有所知的人,或多或少都觉得vi超级难用,但vi是最基本的编辑器,学好了vi,以后在UNIX世 ...

  10. Android常用正则工具类

    此类提供日常开发中常用的正则验证函数,比如:邮箱.手机号.电话号码.身份证号码.日期.数字.小数.URL.IP地址等.使用Pattern对象的matches方法进行整个字符匹配,调用该方法相当于:   ...