UITableView长按拖动排序(支持不同行高,不同section间交换)
效果图:
github下载地址:DHDragableCellTableView
使用
将tableView继承与DHDragableCellTableView并遵循协议DHDragableCellTableViewDataSource,DHDragableCellTableViewDelegate
#pragma mark - DHDragableCellTableViewDataSource- (NSArray *)dataSourceArrayInTableView:(DHDragableCellTableView *)tableView{ return self.dataSource.copy;//数据源}
- (void)tableView:(DHDragableCellTableView *)tableView newDataSourceArrayAfterMove:(NSArray *)newDataSourceArray{ self.dataSource = newDataSourceArray.mutableCopy;//返回的数据源 [self.tableView reloadData];}复制代码
实现:
大概思路,为UITableView添加长按手势,长按后给选择的cell截图并隐藏选择的cell,让截图跟随手势移动
1.添加手势
/** 添加手势 */- (void)dh_addGesture{ _gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(dh_processGesture:)]; _gesture.minimumPressDuration = _gestureMinimumPressDuration; [self addGestureRecognizer:_gesture];}复制代码
2.监听手势状态
- (void)dh_processGesture:(UILongPressGestureRecognizer *)gesture{ switch (gesture.state) { case UIGestureRecognizerStateBegan: { [self dh_gestureBegan:gesture]; } break; case UIGestureRecognizerStateChanged: { if (!_canEdgeScroll) { [self dh_gestureChanged:gesture]; } } break; case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: { [self dh_gestureEndedOrCancelled:gesture]; } break; default: break; }}复制代码
3.开始拖动
- (void)dh_gestureBegan:(UILongPressGestureRecognizer *)gesture{ CGPoint point = [gesture locationInView:gesture.view]; self.lastPoint = point; NSIndexPath *selectedIndexPath = [self indexPathForRowAtPoint:point]; if (!selectedIndexPath) { return; } if (self.delegate && [self.delegate respondsToSelector:@selector(tableView:willMoveCellAtIndexPath:)]) { [self.delegate tableView:self willMoveCellAtIndexPath:selectedIndexPath]; } if (_canEdgeScroll) { //开启边缘滚动 [self dh_startEdgeScroll]; } //每次移动开始获取一次数据源 if (self.dataSource && [self.dataSource respondsToSelector:@selector(dataSourceArrayInTableView:)]) { _tempDataSource = [self.dataSource dataSourceArrayInTableView:self].mutableCopy; } _selectedIndexPath = selectedIndexPath; UITableViewCell *cell = [self cellForRowAtIndexPath:selectedIndexPath]; _tempView = [self dh_snapshotViewWithInputView:cell]; if (_drawMovalbeCellBlock) { //将_tempView通过block让使用者自定义 _drawMovalbeCellBlock(_tempView); }else { //配置默认样式 _tempView.layer.shadowColor = [UIColor grayColor].CGColor; _tempView.layer.masksToBounds = NO; _tempView.layer.cornerRadius = 0; _tempView.layer.shadowOffset = CGSizeMake(-5, 0); _tempView.layer.shadowOpacity = 0.4; _tempView.layer.shadowRadius = 5; } _tempView.frame = cell.frame; [self addSubview:_tempView]; //隐藏cell cell.hidden = YES; [UIView animateWithDuration:kDH_DragableCellAnimationTime animations:^{ _tempView.center = CGPointMake(_tempView.center.x, point.y); }];}复制代码
4.拖动 这里的_toBottom是int类型用来判断手势是向哪一个方向拖动,然后根据拖动的cell跟要交换的cell的中心点进行比较,判断是否交换
- (void)dh_gestureChanged:(UILongPressGestureRecognizer *)gesture{ CGPoint point = [gesture locationInView:gesture.view]; //判断拖动的方向 if (point.y - self.lastPoint.y > 0) { _toBottom = 1;//向下拖 }else if(point.y - self.lastPoint.y < 0){ _toBottom = -1;//向上拖 }else{ _toBottom = 0; } self.lastPoint = point; NSIndexPath *currentIndexPath = [self indexPathForRowAtPoint:point]; if (currentIndexPath && ![_selectedIndexPath isEqual:currentIndexPath]) { UITableViewCell *cell = [self cellForRowAtIndexPath:_selectedIndexPath]; UITableViewCell *cell1 = [self cellForRowAtIndexPath:currentIndexPath]; //将拖动的cell跟要交换的cell的centerY进行比较 if ((_toBottom == 1 && (point.y+cell.frame.size.height/2) >= CGRectGetMaxY(cell1.frame) && (CGRectGetMaxY(cell1.frame) >= CGRectGetMaxY(cell.frame))) || ((_toBottom == -1 && (point.y-cell.frame.size.height/2) <= CGRectGetMinY(cell1.frame)) && (CGRectGetMinY(cell1.frame) <= CGRectGetMinY(cell.frame)))) { //交换数据源和cell [self dh_updateDataSourceAndCellFromIndexPath:_selectedIndexPath toIndexPath:currentIndexPath]; if (self.delegate && [self.delegate respondsToSelector:@selector(tableView:didMoveCellFromIndexPath:toIndexPath:)]) { [self.delegate tableView:self didMoveCellFromIndexPath:_selectedIndexPath toIndexPath:currentIndexPath]; } _selectedIndexPath = currentIndexPath; } } //让截图跟随手势 _tempView.center = CGPointMake(_tempView.center.x, point.y);}复制代码
5.交换数据跟cell的位置 为了在不同行高交换时cell不变形,交换后要立刻reloadData,再通过对两个cell截图,用截图来模拟交换的动画
/**交换数据源 跟 cell的位置*/
- (void)dh_updateDataSourceAndCellFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{if ([self numberOfSections] == 1) {//只有一组[_tempDataSource exchangeObjectAtIndex:fromIndexPath.row withObjectAtIndex:toIndexPath.row];//交换cell[self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath];}else {//有多组id fromData = _tempDataSource[fromIndexPath.section][fromIndexPath.row];id toData = _tempDataSource[toIndexPath.section][toIndexPath.row];NSMutableArray *fromArray = [_tempDataSource[fromIndexPath.section] mutableCopy];NSMutableArray *toArray = [_tempDataSource[toIndexPath.section] mutableCopy];[fromArray replaceObjectAtIndex:fromIndexPath.row withObject:toData];[toArray replaceObjectAtIndex:toIndexPath.row withObject:fromData];[_tempDataSource replaceObjectAtIndex:fromIndexPath.section withObject:fromArray];[_tempDataSource replaceObjectAtIndex:toIndexPath.section withObject:toArray];//交换cell[self beginUpdates];[self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath];[self moveRowAtIndexPath:toIndexPath toIndexPath:fromIndexPath];[self endUpdates];}//交换数据源后reloadData[self reloadData];//返回交换后的数据源if (self.dataSource && [self.dataSource respondsToSelector:@selector(tableView:newDataSourceArrayAfterMove:)]) {[self.dataSource tableView:self newDataSourceArrayAfterMove:_tempDataSource.copy];}//此处用两个cell的截图实现交换的动画UITableViewCell *cell = [self cellForRowAtIndexPath:fromIndexPath];cell.hidden = NO;UITableViewCell *cell1 = [self cellForRowAtIndexPath:toIndexPath];cell1.hidden = YES;UIView *tmpCell = [self dh_snapshotViewWithInputView:cell];cell.hidden = YES;tmpCell.frame = cell1.frame;if (_toBottom == -1) {//向上tmpCell.frame = CGRectMake(0, CGRectGetMinY(cell1.frame), cell.frame.size.width, cell.frame.size.height);[self insertSubview:tmpCell belowSubview:_tempView];}else if (_toBottom == 1) {//向下tmpCell.frame = CGRectMake(0, CGRectGetMaxY(cell1.frame)-cell.frame.size.height, cell.frame.size.width, cell.frame.size.height);[self insertSubview:tmpCell belowSubview:_tempView];}else{}[UIView animateWithDuration:0.2 animations:^{tmpCell.frame = cell.frame;}completion:^(BOOL finished) {cell.hidden = NO;[tmpCell removeFromSuperview];}];
}
复制代码
6.边缘滚动处理
- (void)dh_startEdgeScroll{ _edgeScrollTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(dh_processEdgeScroll)]; [_edgeScrollTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];}
- (void)dh_processEdgeScroll{ [self dh_gestureChanged:_gesture]; CGFloat minOffsetY = self.contentOffset.y + _edgeScrollRange; CGFloat maxOffsetY = self.contentOffset.y + self.bounds.size.height - _edgeScrollRange; CGPoint touchPoint = _tempView.center; //处理上下达到极限之后不再滚动tableView,其中处理了滚动到最边缘的时候,当前处于edgeScrollRange内,但是tableView还未显示完,需要显示完tableView才停止滚动 if (touchPoint.y < _edgeScrollRange) { if (self.contentOffset.y <= 0) { return; }else { if (self.contentOffset.y - 1 < 0) { return; } [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y - 1) animated:NO]; _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y - 1); } } if (touchPoint.y > self.contentSize.height - _edgeScrollRange) { if (self.contentOffset.y >= self.contentSize.height - self.bounds.size.height) { return; }else { if (self.contentOffset.y + 1 > self.contentSize.height - self.bounds.size.height) { return; } [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + 1) animated:NO]; _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y + 1); } } //处理滚动 CGFloat maxMoveDistance = 20; if (touchPoint.y < minOffsetY) { //cell在往上移动 CGFloat moveDistance = (minOffsetY - touchPoint.y)/_edgeScrollRange*maxMoveDistance; [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y - moveDistance) animated:NO]; _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y - moveDistance); }else if (touchPoint.y > maxOffsetY) { //cell在往下移动 CGFloat moveDistance = (touchPoint.y - maxOffsetY)/_edgeScrollRange*maxMoveDistance; [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + moveDistance) animated:NO]; _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y + moveDistance); }}复制代码
转载于:https://juejin.im/post/5a37791f6fb9a044fa19f5a1
UITableView长按拖动排序(支持不同行高,不同section间交换)相关推荐
- iOS tableview左滑编辑,长按拖动排序
有的时候还是想复用iOS自身的设计逻辑,减少代码编写. 本次主要是想实现对于列表的排序,编辑修改,删除操作. 涉及到的操作方式如下: 向左滑动出现编辑及删除选项: 长按列表项目排序: 效果图如下: 先 ...
- 【Android 事件分发】ItemTouchHelper 实现拖动排序
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- Android拖动实现(一个流畅的拖动排序DragSortGridView,自动滚屏)
https://github.com/huxq17/HandyGridView 先上效果 流畅效果超越了网易新闻和UC浏览器的栏目收藏.gif图和实际效果有差距 1.拖拽可以移动item,并且其他it ...
- 移动端上下拖动调整顺序效果_vue实现移动端拖动排序
本文实例为大家分享了vue实现移动端拖动排序的具体代码,供大家参考,具体内容如下 效果 代码: class="childDiv" v-for="(option,index ...
- Android实现GridView的item长按拖动删除完美实现(带动画效果)
领导这几天让做一个项目,就是可以实现像支付宝首页一样的可以长按拖动,删除的界面,以前没做过,领导让我做的时候觉得简直是老虎吃天,无从下手啊,可是领导的任务还是要实现的,没办法,就自己网上找咯,但是网上 ...
- php 长连接心跳_支持gRPC长链接,深度解读Nacos2.0架构设计及新模型
作者 | 杨翊(席翁) Nacos PMC 来源|阿里巴巴云原生公众号 Nacos 简介 Nacos 在阿里巴巴起源于 2008 年五彩石项目,该项目完成了微服务拆分和业务中台建设,随着云计算和开源环 ...
- android 二级列表拖动排序_Excel的数据透视表六种排序方法
Excel的数据透视表排序不像表格中那样操作灵活,很多小伙伴对此不熟悉,本文系统讲解数据透视表的各种排序. 一.常规排序 二.组内排序 三.多关键字排序 四.手动拖动排序 五.手动输入排序 六.设置透 ...
- jquery 鼠标拖动排序Li或Table
1.前端页面 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="拖动排序Li或Ta ...
- 西门子1200/1500PLC不定长数组选择排序的运用编程实例
前景介绍: 1.选择排序原理:选择排序算法首先从第1个位置开始对全部元素进行选择,选出全部元素中最小的给该位置,再对第2个位置进行选择,在剩余元素中选择最小的给该位置即可:以此类推,重复进行" ...
最新文章
- Nature:要想真正研究宿主-肠道微生物的相互作用,必须将相对定量变成绝对定量...
- Redis的三种启动方式【转】
- 201117阶段二SQLite数据库
- Action和Func区别
- A*算法(一)算法导言
- Android学习之路五:Dialog和Toast
- sonar小白式入门
- matlab示例程序,matlab示例程序
- 数据库学生信息管理系统
- 计划bom表 java_ERP总结系列(BOM浅谈)
- latex中自动生成参考文献
- ImportError: cannot import name ‘evaluate‘ from ‘surprise‘解决方案
- 【ArcGIS】空间表无法删除的问题处理
- 入门理解计算机视觉、图形学、图像处理
- Kali实现ARP欺骗
- Flowable 6.6.0 BPMN用户指南 - (5)Spring Boot - 5.8	Flowable应用属性
- python绘制日历图
- 第二十章:异步和文件I/O.(十九)
- [mini-css-extract-plugin] warning Conflicting order
- 越南使用的越南文unicode编码范围
热门文章
- string转int/float/double、int/float/double转string、转字符串数组的方法:stoi、stringstream、scanf、to_string、sprintf
- 【离散数学】滨江学院 期末考试 题库
- Oracle和al,ORACLEAL TERTABLE
- Swift NSDate的一个分类,把Mon Apr 04 19:45:37 +0800 2016这种格式的时间转换为2016-04-04 11:45:37 +0000
- 读《程序员修炼之道——从小工到专家》
- kuangbin专题一 简单搜索
- Android 驱动测试程序H-M-S 6
- js open 和close 方法
- C# 禁止控件重绘(绘制)
- 上学的时候的一个作业