
 
   
   
 
   本文译自:Cookbook: Moving Table View Cells with a Long Press Gesture
 
   目录:
 
  -  你需要什么?
 -  如何做?
 -  如何将其利用至UICollectionView上?
 -  何去何从?
 
 
   本次的 cookbook-style 教程中介绍如何通过长按手势来移动 table view中的cell,这种操作方式就像苹果自家的天气 App 一样。
 
   你可以直接把本文中的到吗添加到你的工程中,或者将其添加到我为你创建好的 starter project 中,也可以下载本文的完整示例工程。
 
   你需要什么?
 
  -  UILongGestureRecognizer
 -  UITableView (可以用 UICollectionView 替代之)
 -  UITableViewController (可以用 UIViewController 或 UICollectionViewController 替代之)
 -  5 分钟。
 
 
   如何做?
 
   首先给 table view 添加一个 UILongGestureRecognizer。可以在 table view controller 的 viewDidLoad 方法中添加。
 
   
       
        1
        
       
        2
        
       
        3
          |  
       
        UILongPressGestureRecognizer *longPress = [[
        UILongPressGestureRecognizer alloc] 
        
       
          initWithTarget:
        self action:
        @selector(longPressGestureRecognized:)];
        
       
        [
        self.tableView addGestureRecognizer:longPress];
          | 
 
   记者为 gesture recognizer 添加 action 方法。该方法首先应该获取到在 table view 中长按的位置,然后找出这个位置对应的 cell 的 index。记住:这里获取到的 index path 有可能为 nil(例如,如果用户长按在 table view的section header上)。
 
   
       
        1
        
       
        2
        
       
        3
        
       
        4
        
       
        5
        
       
        6
        
       
        7
        
       
        8
        
       
        9
        
       
        10
          |  
       
        - (
        IBAction)longPressGestureRecognized:(
        id)sender {
        
        
        
         
        UILongPressGestureRecognizer *longPress = (
        UILongPressGestureRecognizer *)sender;
        
         
        UIGestureRecognizerState state = longPress.state;
        
        
        
         
        CGPoint location = [longPress locationInView:
        self.tableView];
        
         
        NSIndexPath *indexPath = [
        self.tableView indexPathForRowAtPoint:location];
        
        
        
         
        
        
       
        }
          | 
 
   接着你需要处理UIGestureRecognizerStateBegan分支。如果获取到一个有效的 index path(non-nil),就去获取对应的 UITableViewCell,并利用一个 helper 方法获取这个 table view cell 的 snapshot view。然后将这个 snapshot view 添加到 table view 中,并将其 center 到对应的 cell上。
 
   为了更好的用户体验,以及更自然的效果,在这里我把原始 cell 的背景设置为黑色,并给 snapshot view 增加淡入效果,让 snapshot view 比 原始 cell 稍微大一点,将它的Y坐标偏移量与手势的位置的Y轴对齐。这样处理之后,cell 就像从 table view 中跳出,然后浮在上面,并捕捉到用户的手指。
 
   
       
        1
        
       
        2
        
       
        3
        
       
        4
        
       
        5
        
       
        6
        
       
        7
        
       
        8
        
       
        9
        
       
        10
        
       
        11
        
       
        12
        
       
        13
        
       
        14
        
       
        15
        
       
        16
        
       
        17
        
       
        18
        
       
        19
        
       
        20
        
       
        21
        
       
        22
        
       
        23
        
       
        24
        
       
        25
        
       
        26
        
       
        27
        
       
        28
        
       
        29
        
       
        30
        
       
        31
        
       
        32
        
       
        33
        
       
        34
          |  
       
        static 
        UIView       *snapshot = 
        nil;        
        
        
       
        static 
        NSIndexPath  *sourceIndexPath = 
        nil; 
        
        
        
        
       
        switch (state) {
        
         
        case 
        UIGestureRecognizerStateBegan: {
        
           
        if (indexPath) {
        
       
              sourceIndexPath = indexPath;
        
        
        
             
        UITableViewCell *cell = [
        self.tableView cellForRowAtIndexPath:indexPath];
        
        
        
             
        
        
       
              snapshot = [
        self customSnapshotFromView:cell];
        
        
        
             
        
        
       
              __block 
        CGPoint center = cell.center;
        
       
              snapshot.center = center;
        
       
              snapshot.alpha = 
        0.0;
        
       
              [
        self.tableView addSubview:snapshot];
        
       
              [
        UIView animateWithDuration:
        0.25 animations:^{
        
        
        
               
        
        
       
                center.y = location.y;
        
       
                snapshot.center = center;
        
       
                snapshot.transform = 
        CGAffineTransformMakeScale(
        1.05, 
        1.05);
        
       
                snapshot.alpha = 
        0.98;
        
        
        
               
        
        
       
                cell.backgroundColor = [
        UIColor blackColor];
        
       
              } completion:
        nil];
        
       
            }
        
           
        break;
        
       
          }
        
         
        
        
       
        }
          | 
 
   将下面的方法添加到 .m 文件的尾部。该方法会根据传入的 view,返回一个对应的 snapshot view。
 
   
       
        1
        
       
        2
        
       
        3
        
       
        4
        
       
        5
        
       
        6
        
       
        7
        
       
        8
        
       
        9
        
       
        10
        
       
        11
          |  
       
        - (
        UIView *)customSnapshotFromView:(
        UIView *)inputView {
        
        
        
         
        UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:
        YES];
        
       
          snapshot.layer.masksToBounds = 
        NO;
        
       
          snapshot.layer.cornerRadius = 
        0.0;
        
       
          snapshot.layer.shadowOffset = 
        CGSizeMake(
        -5.0, 
        0.0);
        
       
          snapshot.layer.shadowRadius = 
        5.0;
        
       
          snapshot.layer.shadowOpacity = 
        0.4;
        
        
        
         
        return snapshot;
        
       
        }
          | 
 
   当手势移动的时候,也就是UIGestureRecognizerStateChanged分支,此时需要移动 snapshot view(只需要设置它的 Y 轴偏移量即可)。如果手势移动的距离对应到另外一个 index path,就需要告诉 table view,让其移动 rows。同时,你需要对 data source 进行更新:
 
   
       
        1
        
       
        2
        
       
        3
        
       
        4
        
       
        5
        
       
        6
        
       
        7
        
       
        8
        
       
        9
        
       
        10
        
       
        11
        
       
        12
        
       
        13
        
       
        14
        
       
        15
        
       
        16
        
       
        17
        
       
        18
        
       
        19
        
       
        20
          |  
       
        case 
        UIGestureRecognizerStateChanged: {
        
         
        CGPoint center = snapshot.center;
        
       
          center.y = location.y;
        
       
          snapshot.center = center;
        
        
        
         
        
        
         
        if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
        
        
        
           
        
        
       
            [
        self.objects exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
        
        
        
           
        
        
       
            [
        self.tableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];
        
        
        
           
        
        
       
            sourceIndexPath = indexPath;
        
       
          }
        
         
        break;
        
       
        }
        
       
        
          | 
 
   最后,当手势结束或者取消时,table view 和 data source 都是最新的。你所需要做的事情就是将 snapshot view 从 table view 中移除,并把 cell 的背景色还原为白色。
 
   为了提升用户体验,我们将 snapshot view 淡出,并让其尺寸变小至与 cell 一样。这样看起来就像把 cell 放回原处一样。
 
   
       
        1
        
       
        2
        
       
        3
        
       
        4
        
       
        5
        
       
        6
        
       
        7
        
       
        8
        
       
        9
        
       
        10
        
       
        11
        
       
        12
        
       
        13
        
       
        14
        
       
        15
        
       
        16
        
       
        17
        
       
        18
        
       
        19
        
       
        20
        
       
        21
          |  
       
        default: {
        
         
        
        
         
        UITableViewCell *cell = [
        self.tableView cellForRowAtIndexPath:sourceIndexPath];
        
       
          [
        UIView animateWithDuration:
        0.25 animations:^{
        
        
        
       
            snapshot.center = cell.center;
        
       
            snapshot.transform = 
        CGAffineTransformIdentity;
        
       
            snapshot.alpha = 
        0.0;
        
        
        
           
        
        
       
            cell.backgroundColor = [
        UIColor whiteColor];
        
        
        
       
          } completion:^(
        BOOL finished) {
        
        
        
       
            [snapshot removeFromSuperview];
        
       
            snapshot = 
        nil;
        
        
        
       
          }];
        
       
          sourceIndexPath = 
        nil;
        
         
        break;
        
       
        }
          | 
 
   就这样,搞定了!编译并运行程序,现在可以通过长按手势对 tableview cells重新排序!
 
   你可以在 GitHub 上下载到完整的示例工程。
 
   如何将其利用至UICollectionView上?
 
   假设你已经有一个示例工程使用了 UICollectionView,那么你可以很简单的就使用上本文之前介绍的代码。所需要做的事情就是用 self.collectionView替换掉 self.tableView,并更新一下获取和移动 UICollectionViewCell 的调用方法。
 
   这里有个练习,从 GitHub上 checkout 出 UICollectionView 的starter project,然后将 tap-和-hold 手势添加进去以对 cells 进行重排。这里可以下载到已经实现好了工程。