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

大兔子大兔子 提交于 2020-04-17 03:32:26

【推荐阅读】微服务还能火多久?>>>

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


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!