這是一個徹底依靠手勢的操做ToDoList的演示,功能上左劃刪除,右劃完成任務,拖拽調整順序,捏合張開插入。git
項目源碼: https://github.com/luan-ma/ClearStyleDemo.Swift github
TDCToDoItem.swift 定義模型對象web
TDCToDoListController.swift 繼承自UITableViewController, 演示UITableView操做
swift
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就爲有效操做,左劃爲刪除操做,右劃爲完成操做。
less
佈局使用AutoLayout,中間內容區的限制條件是寬度等於容器寬度、高度等於容器高度、垂直中對齊、水平中對齊,而平移操做實際上就是操做水平中對齊的距離值。ide
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) } }
手勢操做lua
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 = 0 func 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中執行刪除操做spa
/* // 簡單刪除 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 = false func 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