博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Swift开发:仿Clear手势操作(拖拽、划动、捏合)UITableView
阅读量:6404 次
发布时间:2019-06-23

本文共 13798 字,大约阅读时间需要 45 分钟。

hot3.png

这是一个完全依靠手势的操作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,中间内容区的限制条件是宽度等于容器宽度、高度等于容器高度、垂直中对齐、水平中对齐,而平移操作实际上就是操作水平中对齐的距离值。

03191236_WRjS.gif

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位置。效果如下

03191236_KP0T.gif

截图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位置。

03191237_1ESU.gif

获取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

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

你可能感兴趣的文章
6421B Lab12 控制和监视网络存储
查看>>
gleez常用汇总数据sql
查看>>
DHCP服务器安装及配置案例
查看>>
2.1Linux系统基础入门
查看>>
设计模式之结构型模式—— 2.7 代理模式
查看>>
新浪、万网前系统架构师高俊峰:统一监控报警平台架构设计思路
查看>>
Ext工具栏Toolbar
查看>>
grep命令
查看>>
hive 行转列
查看>>
我的友情链接
查看>>
centos下 MySQL 5.5.13 CMake 安装笔记
查看>>
JS FormData对象
查看>>
【撸啊撸 Docker】搭建 MySQL 数据库
查看>>
Quartz的cron表达式
查看>>
spark streaming 处理空batch
查看>>
搜索会页面跳转
查看>>
设计模式----建造者模式UML和实现代码
查看>>
企业大型多媒体视频会议源码 服务器端 客户端VC
查看>>
percent 简介
查看>>
Oracle Listener 动态注册 与 静态注册
查看>>