Swift工作笔记Swift

Swift.两种方式实现tableViewCell拖拽功能

2018-11-08  本文已影响28人  王四猫
效果图.gif

实现方式:


iOS11.0以上版本:实现原生代理方法

//MARK: - UITableView ios11以上拖拽drag,dropDelegate
extension EWGroupTableViewController:UITableViewDragDelegate,UITableViewDropDelegate{
    /***
     *  iOS11以上版本,实现UITableViewDragDelegate,UITableViewDropDelegate代理方法,使用原生方式实现拖拽功能.
     */
    @available(iOS 11.0, *)
    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        let item = UIDragItem(itemProvider: NSItemProvider(object: UIImage()))
        return [item]
    }
    // MARK: UITableViewDropDelegate
    @available(iOS 11.0, *)
    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {

    }
    @available(iOS 11.0, *)
    func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
        return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }
    @available(iOS 11.0, *)
    func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
        // Only receive image data
        return session.canLoadObjects(ofClass: UIImage.self)
    }
    /// 这是UITableViewDataSourceDelegate中的方法,但是只有iOS11以上版本拖拽中才用的到,方便查看放在这里.
    /// 当拖拽完成时调用.将tableView数据源更新
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        objc_sync_enter(self)
        let model: EWColorModel = sourceIndexPath.section == 0 ? modelArray[sourceIndexPath.row] : secondModelArray[sourceIndexPath.row]
        if sourceIndexPath.section == 0 {
            modelArray.remove(at: sourceIndexPath.row)
        }else {
            secondModelArray.remove(at: sourceIndexPath.row)
        }
        if destinationIndexPath.section == 0{
            if destinationIndexPath.row > modelArray.count{
                modelArray.append(model)
            }else{
                modelArray.insert(model, at: destinationIndexPath.row)
            }
        }else {
            if destinationIndexPath.row > secondModelArray.count{
                secondModelArray.append(model)
            }else {
                secondModelArray.insert(model, at: destinationIndexPath.row)
            }
        }
        objc_sync_exit(self)
        tableView.reloadData()
    }
}

iOS11.0以下版本:自定义长按拖拽手势方法

1.实现为Cell创建快照imageView方法
    /// 获取cell快照imageView
    private func getImageView(_ cell: UITableViewCell) -> UIImageView {
        UIGraphicsBeginImageContextWithOptions(cell.bounds.size, false, 0)
        cell.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        let imageView = UIImageView(image: image)
        return imageView
    }
2.添加选中cell方法
    /// 长按开始状态调用方法
    private func longPressGestureBegan(_ recognise: UILongPressGestureRecognizer) {
        /// 获取长按手势触发时的接触点
        let currentPoint: CGPoint = recognise.location(in: tableView)
        /// 根据手势初始点获取需要拖拽的cell.indexPath
        guard let currentIndexPath = tableView.indexPathForRow(at: currentPoint) else { return }
        /// 将拖拽cell.index储存
        sourceIndexPath = currentIndexPath
        /// 获取拖拽cell
        currentCell = tableView.cellForRow(at: currentIndexPath ) as? EWDragTableViewCell
        /// 获取拖拽cell快照
        cellImageView = getImageView(currentCell)
        /// 将快照加入到tableView.把拖拽cell覆盖
        cellImageView.frame = currentCell.frame
        tableView.addSubview(cellImageView)
        /// 将选中cell隐藏
        self.currentCell.isHidden = true
    }
3.添加cell拖拽状态下方法
    /// 拖拽手势过程中方法,核心方法,实现拖拽动画和数据的更新
    private func longPressGestureChanged(_ recognise: UILongPressGestureRecognizer) {
        let selectedPoint: CGPoint = recognise.location(in: tableView)
        var selectedIndexPath = tableView.indexPathForRow(at: selectedPoint)
        /// 将手势的点加入touchPoints并保证其内有两个点,即一个初始点,一个结束点,实现cell快照imageView从初始点到结束点的移动动画
        self.touchPoints.append(selectedPoint)
        if self.touchPoints.count > 2 {
            self.touchPoints.remove(at: 0)
        }
        var center = cellImageView.center
        /// 快照center.y值直接移动到手势点Y,可以提醒用户cell已经进入了拖拽状态
        center.y = selectedPoint.y
        // 快照x值随触摸点x值改变量移动,保证用户体验
        let Ppoint = self.touchPoints.first
        let Npoint = self.touchPoints.last
        let moveX = Npoint!.x - Ppoint!.x
        center.x += moveX
        cellImageView.center = center
        guard selectedIndexPath != nil else { return }
        /// 如果手势当前index不同于拖拽cell,则需要moveRow,实现tableView上非拖拽cell的动画,这里还要实现数据源的重置,保证拖拽手势后tableView能正确的展示
        if selectedIndexPath != sourceIndexPath {
            tableView.beginUpdates()
            /// 线程锁
            objc_sync_enter(self)
            var cellmode: EWColorModel
            /// 先更新tableView数据源
            switch sourceIndexPath!.section{
            case 0:
                cellmode = modelArray[sourceIndexPath!.row]
                self.modelArray.remove(at: sourceIndexPath!.row)
                if selectedIndexPath!.row < self.modelArray.count {
                    self.modelArray.insert(cellmode, at: selectedIndexPath!.row)
                }else {
                    self.modelArray.append(cellmode)
                }
            case 1:
                cellmode = secondModelArray[sourceIndexPath!.row]
                self.secondModelArray.remove(at: sourceIndexPath!.row)
                if selectedIndexPath!.row < self.secondModelArray.count {
                    self.secondModelArray.insert(cellmode, at: selectedIndexPath!.row)
                }else {
                    self.secondModelArray.append(cellmode)
                }
            default:
                break
            }
            objc_sync_exit(self)
            /// 调用moveRow方法,修改被隐藏的选中cell位置,保证选中cell和快照imageView在同一个row,实现动画效果
            self.tableView.moveRow(at: sourceIndexPath!, to: selectedIndexPath!)
            tableView.endUpdates()
            sourceIndexPath = selectedIndexPath
        }
    }
4.将之前的方法整合成手势方法赋给cell
    /// 手势方法
    @objc func longPressGesture(_ recognise: UILongPressGestureRecognizer) {
        let currentPoint: CGPoint = recognise.location(in: tableView)
        let currentIndexPath = tableView.indexPathForRow(at: currentPoint)
        guard let indexPath = currentIndexPath else {
            /// 将生成的cellimage清除
            removeCellImageView()
            return
        }
        if indexPath.section == 0 {
            guard indexPath.row < self.modelArray.count else {
                /// 将生成的cellimage清除
                removeCellImageView()
                return
            }
        } else {
            guard indexPath.row < self.secondModelArray.count else {
                /// 将生成的cellimage清除
                removeCellImageView()
                return
            }
        }
        switch recognise.state {
        case .began:
            /// 手势开始状态
            longPressGestureBegan(recognise)
        case .changed:
            /// 手势拖拽状态
            longPressGestureChanged(recognise)
        default:
            /// 手势结束状态
            /// 清空保存的手势点
            self.touchPoints.removeAll()
            /// 将隐藏的cell展示
            if let cell = tableView.cellForRow(at: sourceIndexPath! ){
                cell.isHidden = false
            }
            /// 将生成的cellimage清除
            removeCellImageView()
        }
    }

总结:

iOS11.0以上的版本,因为使用原生方法,所以相对简单些.但是我们实现功能不应该只适用于高版本.所以我们需要在低iOS版本下自己实现同样的功能.

功能其实不复杂,但复杂的是思路.

这里使用的方法总结:

从而在低iOS版本实现了与高版本原生代理方法一样的功能与效果.

在demo项目与效果图展示中,分别展示了一个section和多个section两种形式的tableView的拖拽方法.

其实本质上方法并没有区别,区别主要在于拖拽完成后修改数据源的逻辑,如果项目中需要多个section之间的拖拽功能,一定要多考虑相关问题.


demo地址: EWTableViewCellDragDrop

有问题欢迎探讨.

上一篇下一篇

猜你喜欢

热点阅读