这是一个完全依靠手势的操作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 = 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中执行删除操作
/*
// 简单删除
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
来源:oschina
链接:https://my.oschina.net/u/211651/blog/596540