2019独角兽企业重金招聘Python工程师标准>>>

这是一个完全依靠手势的操作ToDoList的演示,功能上左划删除,右划完成任务,拖拽调整顺序,捏合张开插入。

项目源码: https://github.com/luan-ma/ClearStyleDemo.Swift

初始化

TDCToDoItem.swift   定义模型对象

TDCToDoListController.swift 继承自UITableViewController, 演示UITableView操作

var items = [TDCToDoItem(text: "Feed the cat"),TDCToDoItem(text: "Buy eggs"),TDCToDoItem(text: "Pack bags for WWDC"),TDCToDoItem(text: "Rule the web"),TDCToDoItem(text: "Buy a new iPhone"),TDCToDoItem(text: "Find missing socks"),TDCToDoItem(text: "Write a new tutorial"),TDCToDoItem(text: "Master Objective-C"),TDCToDoItem(text: "Remember your wedding anniversary!"),TDCToDoItem(text: "Drink less beer"),TDCToDoItem(text: "Learn to draw"),TDCToDoItem(text: "Take the car to the garage"),TDCToDoItem(text: "Sell things on eBay"),TDCToDoItem(text: "Learn to juggle"),TDCToDoItem(text: "Give up")
]override func viewDidLoad() {super.viewDidLoad()//捏合手势let pinch = UIPinchGestureRecognizer(target: self, action: "handlePinch:")//长按拖拽let longPress = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")tableView.addGestureRecognizer(pinch)tableView.addGestureRecognizer(longPress)
}


左划删除、右划完成

在每一个Cell添加滑动手势(Pan)。处理划动距离,超过宽度1/3就为有效操作,左划为删除操作,右划为完成操作。

布局使用AutoLayout,中间内容区的限制条件是宽度等于容器宽度、高度等于容器高度、垂直中对齐、水平中对齐,而平移操作实际上就是操作水平中对齐的距离值。

TDCToDoItemCell.swift关键代码如下

手势判断

// 如果是划动手势,仅支持左右划动;如果是其它手势,则有父类负责
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {if let panGesture = gestureRecognizer as? UIPanGestureRecognizer {let translation = panGesture.translationInView(self.superview)return fabs(translation.x) > fabs(translation.y)} else {return super.gestureRecognizerShouldBegin(gestureRecognizer)}
}

手势操作

var onDelete: ((TDCToDoItemCell) -> Void)?
var onComplete: ((TDCToDoItemCell) -> Void)?private var deleteOnDragRelease: Bool = false
private var completeOnDragRelease: Bool = false// 划动平移的实际AutoLayout中的水平中对齐的距离
@IBOutlet weak var centerConstraint: NSLayoutConstraint!
private var originConstant: CGFloat = 0func handlePan(panGesture: UIPanGestureRecognizer) {switch panGesture.state {case .Began:originConstant = centerConstraint.constantcase .Changed:let translation = panGesture.translationInView(self)centerConstraint.constant = translation.x// 划动移动1/3宽度为有效划动let finished = fabs(translation.x) > CGRectGetWidth(bounds) / 3if translation.x < originConstant { // 右划if finished {deleteOnDragRelease = truerightLabel.textColor = UIColor.redColor()} else {deleteOnDragRelease = falserightLabel.textColor = UIColor.whiteColor()}} else { // 左划if finished {completeOnDragRelease = trueleftLabel.textColor = UIColor.greenColor()} else {completeOnDragRelease = falseleftLabel.textColor = UIColor.whiteColor()}}case .Ended:centerConstraint.constant = originConstantif deleteOnDragRelease {deleteOnDragRelease = falseif let onDelete = onDelete {onDelete(self)}}if completeOnDragRelease {completeOnDragRelease = falseif let onComplete = onComplete {onComplete(self)}}default:break}
}

TDCToDoListController.swift中执行删除操作

/*
// 简单删除
func deleteToDoItem(indexPath: NSIndexPath) {tableView.beginUpdates()items.removeAtIndex(indexPath.row)tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)tableView.endUpdates()
}
*/// 视觉效果更漂亮的删除
func deleteToDoItem(indexPath: NSIndexPath) {let item = items.removeAtIndex(indexPath.row)var animationEnabled = falselet lastCell = tableView.visibleCells.lastvar delay: NSTimeInterval = 0for cell in tableView.visibleCells {let cell = cell as! TDCToDoItemCellif animationEnabled {UIView.animateWithDuration(0.25, delay: delay, options: .CurveEaseInOut,animations: { () -> Void incell.frame = CGRectOffset(cell.frame, 0, -CGRectGetHeight(cell.frame))}, completion: { (completed) -> Void inif cell == lastCell {self.tableView.reloadData()}})delay += 0.03}if cell.toDoItem == item {animationEnabled = truecell.hidden = true}}
}

拖拽排序

长按选中某Cell,截图此Cell生成UIImageView,然后隐藏此Cell(hidden=true),随手指移动拖拽UIImageView,每次拖拽到一个Cell上的时候,交换当前Cell和隐藏Cell位置。效果如下

截图UIView生成一个新的UIImageVIew

func snapView(view: UIView) -> UIImageView {UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0)view.layer.renderInContext(UIGraphicsGetCurrentContext()!)let image = UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()let snapShot = UIImageView(image: image)snapShot.layer.masksToBounds = false;snapShot.layer.cornerRadius = 0;snapShot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);snapShot.layer.shadowOpacity = 0.4;snapShot.layer.shadowRadius = 5;snapShot.frame = view.framereturn snapShot
}

拖拽操作代码,详细操作参考注释

private var sourceIndexPath: NSIndexPath?
private var snapView: UIView?func handleLongPress(longPress: UILongPressGestureRecognizer) {let point = longPress.locationInView(tableView)if let indexPath = tableView.indexPathForRowAtPoint(point) {switch longPress.state {case .Began:if let cell = tableView.cellForRowAtIndexPath(indexPath) {sourceIndexPath = indexPathlet snapView = self.snapView(cell)snapView.alpha = 0self.snapView = snapViewtableView.addSubview(snapView)UIView.animateWithDuration(0.25, animations: {// 选中Cell跳出放大效果snapView.alpha = 0.95snapView.center = CGPointMake(cell.center.x, point.y)snapView.transform = CGAffineTransformMakeScale(1.05, 1.05)cell.alpha = 0}, completion: { (completed) -> Void incell.hidden = truecell.alpha = 1})} else {sourceIndexPath = nilsnapView = nilbreak}case .Changed:if let snapView = snapView {// 截图随手指上下移动snapView.center = CGPointMake(snapView.center.x, point.y)}// 如果手指移动到一个新的Cell上面,隐藏Cell跟此Cell交换位置if let fromIndexPath = sourceIndexPath {if fromIndexPath != indexPath {tableView.beginUpdates()let temp = items[indexPath.row]items[indexPath.row] = items[fromIndexPath.row]items[fromIndexPath.row] = temptableView.moveRowAtIndexPath(fromIndexPath, toIndexPath: indexPath)tableView.endUpdates()sourceIndexPath = indexPath}}// 手指移动到屏幕顶端或底部,UITableView自动滚动let step: CGFloat = 64if let parentView = tableView.superview {let parentPos = tableView.convertPoint(point, toView: parentView)if parentPos.y > parentView.bounds.height - step {var offset = tableView.contentOffsetoffset.y += (parentPos.y - parentView.bounds.height + step)if offset.y > tableView.contentSize.height - tableView.bounds.height {offset.y = tableView.contentSize.height - tableView.bounds.height}tableView.setContentOffset(offset, animated: false)} else if parentPos.y <= step {var offset = tableView.contentOffsetoffset.y -= (step - parentPos.y)if offset.y < 0 {offset.y = 0}tableView.setContentOffset(offset, animated: false)}}default:if let snapView = snapView, let fromIndexPath = sourceIndexPath, let cell = tableView.cellForRowAtIndexPath(fromIndexPath) {cell.alpha = 0cell.hidden = false// 长按移动结束,隐藏的Cell恢复显示,删除截图UIView.animateWithDuration(0.25, animations: { () -> Void insnapView.center = cell.centersnapView.alpha = 0cell.alpha = 1}, completion: { [unowned self] (completed) -> Void insnapView.removeFromSuperview()self.snapView = nilself.sourceIndexPath = nilself.tableView.performSelector("reloadData", withObject: nil, afterDelay: 0.5)})}}}
}

捏合张开插入

通过捏合手势中两个触点获取两个相邻的Cell,通过修改UIView.transform属性移动屏幕上的Cell位置。

获取Pinch两个手指坐标的工具方法

func pointsOfPinch(pinch: UIPinchGestureRecognizer) -> (CGPoint, CGPoint) {if pinch.numberOfTouches() > 1 {let point1 = pinch.locationOfTouch(0, inView: tableView)let point2 = pinch.locationOfTouch(1, inView: tableView)if point1.y <= point2.y {return (point1, point2)} else {return (point2, point1)}} else {let point = pinch.locationOfTouch(0, inView: tableView)return (point, point)}
}

捏合张开操作代码,详情请参考代码和注释

// 插入点
private var pinchIndexPath: NSIndexPath?
// 临时代理视图
private var placheHolderCell: TDCPlaceHolderView?
// 两触点的起始位置
private var sourcePoints: (upperPoint: CGPoint, downPoint: CGPoint)?
// 可以插入操作的标志
private var pinchInsertEnabled = falsefunc handlePinch(pinch: UIPinchGestureRecognizer) {switch pinch.state {case .Began:pinchBegan(pinch)case .Changed:pinchChanged(pinch)default:pinchEnd(pinch)}
}func pinchBegan(pinch: UIPinchGestureRecognizer) {pinchIndexPath = nilsourcePoints = nilpinchInsertEnabled = falselet (upperPoint, downPoint) = pointsOfPinch(pinch)if let upperIndexPath = tableView.indexPathForRowAtPoint(upperPoint),let downIndexPath = tableView.indexPathForRowAtPoint(downPoint) {if downIndexPath.row - upperIndexPath.row == 1 {let upperCell = tableView.cellForRowAtIndexPath(upperIndexPath)!let placheHolder = NSBundle.mainBundle().loadNibNamed("TDCPlaceHolderView", owner: tableView, options: nil).first as! TDCPlaceHolderViewplacheHolder.frame = CGRectOffset(upperCell.frame, 0, CGRectGetHeight(upperCell.frame) / 2)tableView.insertSubview(placheHolder, atIndex: 0)sourcePoints = (upperPoint, downPoint)pinchIndexPath = upperIndexPathplacheHolderCell = placheHolder}}
}func pinchChanged(pinch: UIPinchGestureRecognizer) {if let pinchIndexPath = pinchIndexPath, let originPoints = sourcePoints, let placheHolderCell = placheHolderCell {let points = pointsOfPinch(pinch)let upperDistance = points.0.y - originPoints.upperPoint.ylet downDistance = originPoints.downPoint.y - points.1.ylet distance = -min(0, min(upperDistance, downDistance))NSLog("distance=\(distance)")// 移动两边的Cellfor cell in tableView.visibleCells {let indexPath = tableView.indexPathForCell(cell)!if indexPath.row <= pinchIndexPath.row {cell.transform = CGAffineTransformMakeTranslation(0, -distance)} else {cell.transform = CGAffineTransformMakeTranslation(0, distance)}}// 插入的Cell变形let scaleY = min(64, fabs(distance) * 2) / CGFloat(64)placheHolderCell.transform = CGAffineTransformMakeScale(1, scaleY)placheHolderCell.lblTitle.text = scaleY <= 0.5 ? "张开双指插入新项目": "松手可以插入新项目"// 张开超过一个Cell高度时,执行插入操作pinchInsertEnabled = scaleY >= 1}
}func pinchEnd(pinch: UIPinchGestureRecognizer) {if let pinchIndexPath = pinchIndexPath, let placheHolderCell = placheHolderCell {placheHolderCell.transform = CGAffineTransformIdentityplacheHolderCell.removeFromSuperview()self.placheHolderCell = nilif pinchInsertEnabled {// 恢复各Cell的transformfor cell in self.tableView.visibleCells {cell.transform = CGAffineTransformIdentity}// 插入操作let index = pinchIndexPath.row + 1items.insert(TDCToDoItem(text: ""), atIndex: index)tableView.reloadData()// 弹出键盘let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as! TDCToDoItemCellcell.txtField.becomeFirstResponder()} else {// 放弃插入,恢复原位置UIView.animateWithDuration(0.25, delay: 0, options: .CurveEaseInOut, animations: { [unowned self] () -> Void infor cell in self.tableView.visibleCells {cell.transform = CGAffineTransformIdentity}}, completion: { [unowned self] (completed) -> Void inself.tableView.reloadData()})}}sourcePoints = nilpinchIndexPath = nilpinchInsertEnabled = false
}

参考

1. https://github.com/ColinEberhardt/iOS-ClearStyle

2. http://blog.csdn.net/u013604612/article/details/43884039

转载于:https://my.oschina.net/u/211651/blog/596540

Swift开发:仿Clear手势操作(拖拽、划动、捏合)UITableView相关推荐

  1. vuedraggable能实现自由拖拽功能吗?_基于 vue.js 仿禅道主页拖拽效果

    今天给大家分享一个超不错的Vue仿禅道首页拖拽布局VueDndKon. vue-dnd-kon 基于vuedraggable实现的仿禅道首页拖拽项目.支持模块上下及左右自由拖动布局. 主页分为左右两栏 ...

  2. 利用JavaFx开发RIA桌面应用-文件拖拽

    转载请注明来源-作者@loongshawn:http://blog.csdn.net/loongshawn/article/details/53023429 1 背景 给JavaFx中的TextFie ...

  3. 微信小程序之『仿 QQ 消息气泡拖拽消失』

    转载:请写明掘金原文链接及作者名 '小小小' 一个潜心研究小程序QQ群:139128168 ← 点击加群 今天带来的是仿QQ消息气泡拖拽消失特效,源码中很多地方还是有很多不足,希望大家一起齐心协力,给 ...

  4. Silverlight 游戏开发:可重用的拖拽控件

    游戏中有各种各样的拖拽需求,大到窗口,小到图标,在游戏界面操作中,点击和拖拽占据了用户操作的大部分行为,如何做好一个拖拽控件至关重要,做一个可重用的拖拽控件更加重要,我的这些实现方法可能比较另类,但只 ...

  5. Android仿探探卡片拖拽,Vue 仿探探拖拽卡片的效果

    原标题:Vue 仿探探拖拽卡片的效果 已更新Vue3版,请给前端大全发送关键字vue3仿探探获取Vue3版 类似 Tinder 和 探探 的卡片效果的组件,社区中已经非常多了.我这一版除了可以实现和他 ...

  6. 仿QQ消息气泡拖拽效果

    此次的自定义View是仿qq消息列表,消息气泡拖拽效果. 1.原理介绍:自定义view,绘制原始点圆,touch点圆,然后将两圆用贝塞尔曲线连接并填充. 2.应用WindowManager,将自定义v ...

  7. 现代 React Web 开发实战——kanban实现卡片拖拽

    前提摘要: 学习宋一玮 React 新版本 + 函数组件 &Hooks 优先 开篇就是函数组件+Hooks 实现的效果如下: 学到第11篇了 照葫芦画瓢,不过老师在讲解的过程中没有考虑拖拽目标 ...

  8. android仿小红书图片拖拽(改进版,仿微信朋友圈拖拽删除)

    一.小红书效果 上面三个图是小红书发布动态的时候选择好图片后,长按图片进行排序的效果.长按后,选择的图片浮起,随手指左右移动,靠近左右边缘的时候,整体的条目可以左右滚动,再将手指选择的图片发到合适的位 ...

  9. html可移动的悬浮按钮,js仿苹果悬浮可拖拽按钮,并且点击展开效果

    今天写了一个仿苹果的悬浮按钮,由于只在右侧展开,所以只能上下拖拽,展开效果入下 1.html 2.css @charset "gb2312"; .info-nr {position ...

最新文章

  1. C++走向远洋——39(指向学生类的指针)
  2. c语言程序中unit怎么定义,c ++中的一个定义规则(One definition rule in c++)
  3. 利用BBRSACryptor实现iOS端的RSA加解密
  4. 【three.js】库
  5. 模型评价(一) AUC大法 混淆矩阵
  6. 贡献分选择结果——Teamwork
  7. ANR问题的log位置
  8. 最强大脑记忆曲线(4)——关于项目中的全局变量
  9. 联想服务器安装系统鼠标失灵,ThinkPad自行安装操作系统后键盘鼠标失灵怎么办...
  10. 社交规则:饭后抢着买单到底是客气还是客套?大多并不是真心的
  11. JAVA之基数排序LSD顺序
  12. 敏捷结果30天之第十一天:高效能、慢生活
  13. 如何利用免费工具轻松实现个人号裂变?
  14. html css 模仿小米官网搜索框
  15. windows7环境下theano安装
  16. 哥白尼气候数据ERA5数据集——大气数据研究
  17. 电子信息工程跨考计算机武大,我考研的一些经历吧——电气(武汉大学)
  18. JavaScript对象和函数表达式
  19. 第8章 Drupal 主题系统( Drupal theme)(3) 模板文件
  20. ASEMI整流桥DB107详细参数,DB107特征,DB107机械数据

热门文章

  1. 转:java中数组与List相互转换的方法
  2. WCF中常见的几种Host,承载WCF服务的方法详解
  3. Kubernetes最佳实践S01E05:如何优雅地终止
  4. 再遭质疑:Chrome、Safari自动填信息可能会泄密
  5. awk 系列:awk 怎么读取标准输入(STDIN)
  6. CAS做单点登陆(SSO)——集成BIEE 11g
  7. [转]css选择器优先级深入理解
  8. (python)Graph_tools模块学习
  9. How Tomcat Works(十一)
  10. flex4实现图片的动态切换