1.长按即可触发移动cell,操作逻辑简单;
2.移动cell时越靠近屏幕边缘,速度越快;
3.被移动cell的样式可以自定义;
调研
如果只是实现移动UITableViewCell,系统自带的API即可搞定。
调用下面的方法[tableView setEditing:YES animated:YES];即可进入编辑模式。然后实现下面的方法即可开启移动cell。
//默认编辑模式下,每个cell左边有个红色的删除按钮,设置为None即可去掉
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleNone;
}
//是否允许indexPath的cell移动
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
//更新数据源
}
用系统的方法有几个缺点:
1.需要用一个开关去控制编辑状态,不方便;
2.移动cell的时候cell右边有个指示图标,看着不爽;
3.被移动cell的样式不能自己定制。
综上所述,需要自己去写效果了。
实现原理
大概原理:为UITableView添加一个长按手势,然后给选中的cell截图;让截图随着手势移动,同时记录选中的indexPath,方便位置互换。
1.添加长按手势
longPressGesutre = UILongPressGestureRecognizer.init(target: self, action:#selector(HomePageViewController.handleLongpressGesture(_:)))
longPressGesutre.minimumPressDuration = 0.5
self.tableView.addGestureRecognizer(longPressGesutre)
2.处理手势开始
func handleLongpressGesture(sender:UILongPressGestureRecognizer) {
switch sender.state {
case UIGestureRecognizerState.Began:
gestureBegan(sender)
break
case UIGestureRecognizerState.Changed:
gestureChanged(sender)
break
case UIGestureRecognizerState.Cancelled:
gestureEnded(sender)
break
case UIGestureRecognizerState.Ended:
gestureEnded(sender)
break
default:
break
}
}
3 开始处理手势
private func gestureBegan(gesture:UILongPressGestureRecognizer) {
let location:CGPoint = gesture.locationInView(self.tableView)
let indexPath:NSIndexPath! = self.tableView.indexPathForRowAtPoint(location)
if indexPath != nil {
// 开启边缘滚动
startEdgeScroll()
sourceIndexPath = indexPath
let cell:HomeSubTableViewCell = self.tableView.cellForRowAtIndexPath(indexPath) as! HomeSubTableViewCell
snapshot = self.customSnapshotFromView(cell)
var center:CGPoint = cell.center
snapshot.center = center
snapshot.alpha = 0
self.tableView.addSubview(snapshot)
UIView.animateWithDuration(0.25, animations: {
center.y = location.y
self.snapshot.center = center
self.snapshot.transform = CGAffineTransformMakeScale(1, 1)
self.snapshot.alpha = 1.0
self.snapshotColor = cell.backgroundColor
cell.backgroundColor = UIColor.clearColor()
cell.hidden = true
}, completion: nil)
}
}
4 处理手势状态变化,并交换数据源
private func gestureChanged(gesture:UILongPressGestureRecognizer) {
let location:CGPoint = gesture.locationInView(self.tableView)
let indexPath:NSIndexPath! = self.tableView.indexPathForRowAtPoint(location)
var center:CGPoint = snapshot.center
center.y = location.y
snapshot.center = center
if indexPath != nil && indexPath.isEqual(sourceIndexPath) == false {
let cellA:HomeSubTableViewCell = tableView.cellForRowAtIndexPath(sourceIndexPath) as! HomeSubTableViewCell
let cellB:HomeSubTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as! HomeSubTableViewCell
let modelToMove:HomePageCellModel = self.dataArr![sourceIndexPath.row]
self.dataArr!.removeAtIndex(sourceIndexPath.row)
self.dataArr!.insert(modelToMove, atIndex: indexPath.row)
StoreDataModel.sharedInstance.changeChooseRate((cellA.model?.nameEn)!, codeB: (cellB.model?.nameEn)!)
self.tableView.moveRowAtIndexPath(sourceIndexPath, toIndexPath: indexPath)
sourceIndexPath = indexPath
}
}
5 手势结束或者取消,结束之后将视图还原
private func gestureEnded(gesture:UILongPressGestureRecognizer) {
if edgeScrollTimer != nil {
edgeScrollTimer.invalidate()
edgeScrollTimer = nil
}
let cell:HomeSubTableViewCell = self.tableView.cellForRowAtIndexPath(sourceIndexPath) as! HomeSubTableViewCell
UIView.animateWithDuration(0.25, animations: {
self.snapshot.center = cell.center
self.snapshot.transform = CGAffineTransformIdentity
self.snapshot.alpha = 0
cell.backgroundColor = self.snapshotColor
cell.hidden = false
}, completion: { (finished:Bool) in
self.snapshot.removeFromSuperview()
self.snapshot = nil
})
sourceIndexPath = nil
}
6.处理边缘滚动
///开始边界翻滚
private func startEdgeScroll() {
edgeScrollTimer = CADisplayLink.init(target: self, selector: #selector(processEdgeScroll))
edgeScrollTimer.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
@objc private func processEdgeScroll() {
gestureChanged(longPressGesutre)
let minOffsetY:CGFloat = self.tableView.contentOffset.y + 150
let maxOffsetY:CGFloat = self.tableView.contentOffset.y + self.tableView.bounds.size.height - 150
let touchPoint = snapshot.center
//处理上下达到极限之后不再滚动tableView,其中处理了滚动到最边缘的时候,当前处于edgeScrollRange内,但是tableView还未显示完,需要显示完tableView才停止滚动
if touchPoint.y < 150 {
if self.tableView.contentOffset.y <= 0 {
return
} else {
if self.tableView.contentOffset.y - 1 < 0 {
return;
}
self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x
, self.tableView.contentOffset.y), animated: false)
snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y - 1)
}
}
if touchPoint.y > self.tableView.contentSize.height - 150 {
if self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.bounds.size.height {
return
} else {
if self.tableView.contentOffset.y + 1 > self.tableView.contentSize.height - self.tableView.bounds.size.height {
return;
}
self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + 1), animated: false)
snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y + 1)
}
}
// 处理滚动
let maxMoveDistance:CGFloat = 20
if touchPoint.y < minOffsetY {
// cell在向上滚动
let moveDistance:CGFloat = (minOffsetY - touchPoint.y) / 150 * maxMoveDistance
self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y - moveDistance), animated: false)
snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y - moveDistance)
} else if touchPoint.y > maxOffsetY {
// cell在向下滚动
let moveDistance:CGFloat = (touchPoint.y - maxOffsetY) / 150 * maxMoveDistance
self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + moveDistance), animated: false)
snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y + moveDistance)
}
}
总结
通过截图实现以假乱真,自由移动截图产生的”cell”;交换的时候,首先要交换数据源,再交换cell;处理边缘滚动的时候,需要小心各种边界条件。