这是一个完全依靠手势的操作ToDoList的演示,功能上左划删除,右划完成任务,拖拽调整顺序,捏合张开插入。
项目源码:
初始化
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 = falseprivate 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.constant case .Changed: let translation = panGesture.translationInView(self) centerConstraint.constant = translation.x // 划动移动1/3宽度为有效划动 let finished = fabs(translation.x) > CGRectGetWidth(bounds) / 3 if translation.x < originConstant { // 右划 if finished { deleteOnDragRelease = true rightLabel.textColor = UIColor.redColor() } else { deleteOnDragRelease = false rightLabel.textColor = UIColor.whiteColor() } } else { // 左划 if finished { completeOnDragRelease = true leftLabel.textColor = UIColor.greenColor() } else { completeOnDragRelease = false leftLabel.textColor = UIColor.whiteColor() } } case .Ended: centerConstraint.constant = originConstant if deleteOnDragRelease { deleteOnDragRelease = false if let onDelete = onDelete { onDelete(self) } } if completeOnDragRelease { completeOnDragRelease = false if 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 = false let lastCell = tableView.visibleCells.last var delay: NSTimeInterval = 0 for cell in tableView.visibleCells { let cell = cell as! TDCToDoItemCell if animationEnabled { UIView.animateWithDuration(0.25, delay: delay, options: .CurveEaseInOut, animations: { () -> Void in cell.frame = CGRectOffset(cell.frame, 0, -CGRectGetHeight(cell.frame)) }, completion: { (completed) -> Void in if cell == lastCell { self.tableView.reloadData() } }) delay += 0.03 } if cell.toDoItem == item { animationEnabled = true cell.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.frame return 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 = indexPath let snapView = self.snapView(cell) snapView.alpha = 0 self.snapView = snapView tableView.addSubview(snapView) UIView.animateWithDuration(0.25, animations: { // 选中Cell跳出放大效果 snapView.alpha = 0.95 snapView.center = CGPointMake(cell.center.x, point.y) snapView.transform = CGAffineTransformMakeScale(1.05, 1.05) cell.alpha = 0 }, completion: { (completed) -> Void in cell.hidden = true cell.alpha = 1 }) } else { sourceIndexPath = nil snapView = nil break } 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] = temp tableView.moveRowAtIndexPath(fromIndexPath, toIndexPath: indexPath) tableView.endUpdates() sourceIndexPath = indexPath } } // 手指移动到屏幕顶端或底部,UITableView自动滚动 let step: CGFloat = 64 if let parentView = tableView.superview { let parentPos = tableView.convertPoint(point, toView: parentView) if parentPos.y > parentView.bounds.height - step { var offset = tableView.contentOffset offset.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.contentOffset offset.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 = 0 cell.hidden = false // 长按移动结束,隐藏的Cell恢复显示,删除截图 UIView.animateWithDuration(0.25, animations: { () -> Void in snapView.center = cell.center snapView.alpha = 0 cell.alpha = 1 }, completion: { [unowned self] (completed) -> Void in snapView.removeFromSuperview() self.snapView = nil self.sourceIndexPath = nil self.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 = nil sourcePoints = nil pinchInsertEnabled = false let (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! TDCPlaceHolderView placheHolder.frame = CGRectOffset(upperCell.frame, 0, CGRectGetHeight(upperCell.frame) / 2) tableView.insertSubview(placheHolder, atIndex: 0) sourcePoints = (upperPoint, downPoint) pinchIndexPath = upperIndexPath placheHolderCell = 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.y let downDistance = originPoints.downPoint.y - points.1.y let distance = -min(0, min(upperDistance, downDistance)) NSLog("distance=\(distance)") // 移动两边的Cell for 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 = CGAffineTransformIdentity placheHolderCell.removeFromSuperview() self.placheHolderCell = nil if pinchInsertEnabled { // 恢复各Cell的transform for cell in self.tableView.visibleCells { cell.transform = CGAffineTransformIdentity } // 插入操作 let index = pinchIndexPath.row + 1 items.insert(TDCToDoItem(text: ""), atIndex: index) tableView.reloadData() // 弹出键盘 let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as! TDCToDoItemCell cell.txtField.becomeFirstResponder() } else { // 放弃插入,恢复原位置 UIView.animateWithDuration(0.25, delay: 0, options: .CurveEaseInOut, animations: { [unowned self] () -> Void in for cell in self.tableView.visibleCells { cell.transform = CGAffineTransformIdentity } }, completion: { [unowned self] (completed) -> Void in self.tableView.reloadData() }) } } sourcePoints = nil pinchIndexPath = nil pinchInsertEnabled = false}
参考
1. https://github.com/ColinEberhardt/iOS-ClearStyle
2. http://blog.csdn.net/u013604612/article/details/43884039