文档章节

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

luan.ma
 luan.ma
发布于 2016/01/03 19:12
字数 1543
阅读 211
收藏 1

这是一个完全依靠手势的操作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


© 著作权归作者所有

luan.ma
粉丝 3
博文 11
码字总数 2921
作品 0
通州
程序员
私信 提问
[非凡程序员]ScollView 故事板传值

//tableviewXib @interface ViewController : UIViewController<UITableViewDelegate,UITableViewDataSource> @property (weak, nonatomic) IBOutlet UITableView tableview; @property(nona......

TimeConcept
2015/11/18
0
0
IOS高访新浪微博界面(讲解如何自定义UITableViewCell,处理@#链接 特殊字符)

在开发过程中,有好多应用都会嵌入新浪微博的界面,今天整理一下代码。 首先看界面效果图: Demo下载地址:http://download.csdn.net/detail/rhljiayou/6760745 思路:首先放一个UITableView...

长平狐
2013/12/25
251
0
关于searchbar的ScopeButton - 从无到有,纪录编程中的点点滴滴

效果图: 首先在xib里面拖一个“Searchbar and Search Display”,记得还要放一个tableview在上面 再于.h 文件中的iboutlet 变量 UISearchBar *searchBar 关联, 如何关联就不多说了,在xib里...

metRooooo
2013/02/18
0
0
ios学习--TableView详细解释

ios学习--TableView详细解释 分类: ios Object-C2012-05-17 08:48 1714人阅读 评论(0) 收藏 举报 -、建立 UITableView (NSArray )sectionIndexTitlesForTableView:(UITableView )tableView......

wcj
2012/11/01
0
0
一个多UITableview的左右滑动简单解决方案

前言 本文源自实际开发中的需求,核心的要求有几个: 1、多个UITableview要支持左右滑动; 2、点击Tab也要有UITableview的滑动切换效果; 3、每个UITableview单独的下拉刷新和上拉加载; 效果...

落影loyinglin
2018/04/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Tedis:基于 TiKV 构建的 NoSQL 数据库

作者介绍: 陈东明,饿了么北京技术中心架构组负责人,负责饿了么的产品线架构设计以及饿了么基础架构研发工作。曾任百度架构师,负责百度即时通讯产品的架构设计。具有丰富的大规模系统构 ...

TiDB
32分钟前
0
0
linux命令

ls命令是linux下最常用的命令。ls命令就是list的缩写,缺省下ls用来打印出当前目录的清单。如果ls指定其他目录,那么就会显示指定目录里的文件及文件夹清单。 通过ls 命令不仅可以查看linux文件...

WinkJie
39分钟前
1
0
你需要的物流运输类报表,这里都有

你需要的物流运输类报表,都在这里 葡萄城报表模板库是一款免费的报表制作、学习和参考工具,包含了超过 200 张高质量报表模板,涵盖了 16 大行业和 50 多种报表类型,为 30 余万报表开发者提...

葡萄城技术团队
46分钟前
3
0
像Java SE一样编写Java EE(ddd探索)

今天主要改写昨天的组合模式成Web系统。 容器接口为 public interface TreeProduct { /** * 展示所有产品 * @return */ List<TreeProduct> allProducts();...

算法之名
47分钟前
0
0
Django Model 模型建立

Django Model 模型 Django Model层是Django的数据模型层,每一个Model类就是数据库中的一张表; 我们需要注意下面几点: model一般都是定义在不同的APP的models.py模块文件中,可以是一个,也...

彩色泡泡糖
56分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部