Swift.两种方式实现tableViewCell拖拽功能
2018-11-08 本文已影响28人
王四猫
效果图.gif
实现方式:
-
iOS11.0以上版本:
实现UITableViewDragDelegate,UITableViewDropDelegate代理方法,使用原生方式实现拖拽功能. -
iOS11.0以下版本:
为cell添加长按拖拽手势方法.具体看文末总结.
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版本下自己实现同样的功能.
功能其实不复杂,但复杂的是思路.
这里使用的方法总结:
-
为cell添加长按手势方法,当触发方法时,将选中cell隐藏,并同时创建一个与cell一样外观的imageView快照.我们拖拽的其实不是选中的cell,而是加入tableView的快照.
-
当拖拽point超过选中cell.frame时,就手动调用moveRow方法,从而实现了未选中cell为我们拖拽cell让位置的视觉效果.
-
当拖拽手势结束时,将tableView的数据源更改,取消选中cell的隐藏,删除快照imageView,并调用reloadData
从而在低iOS版本实现了与高版本原生代理方法一样的功能与效果.
在demo项目与效果图展示中,分别展示了一个section和多个section两种形式的tableView的拖拽方法.
其实本质上方法并没有区别,区别主要在于拖拽完成后修改数据源的逻辑,如果项目中需要多个section之间的拖拽功能,一定要多考虑相关问题.
demo地址: EWTableViewCellDragDrop
有问题欢迎探讨.