Kodeco Forums

Cookbook: Moving Table View Cells with a Long Press Gesture

Learn how to move table view cells with a long press gesture!


This is a companion discussion topic for the original entry at http://www.raywenderlich.com/63089/cookbook-moving-table-view-cells-with-a-long-press-gesture

__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.alpha = 0.0;
      
    } completion:^(BOOL finished) {
      
      cell.hidden = YES;
      
    }];

In the above code i can do these same set of operations without using __block and
^{}. then what is the purpose of using block here.

Sorry, Iā€™m not sure what you mean that it would work without __block directive. __block allows updating center in the animation block; otherwise you get a compilation error: Read-only variable is not assignable.
^{} is also for the animation block, and it captures center variable. Without __block, center is captured by the block as a read-only variable. By using __block directive, you make mutable inside the animation block.

Swift version(made with 2.2)

var snapShot: UIView?
var sourceIndexPath: NSIndexPath?

func addLongGestureRecognizerForTableView() {
  let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.handleTableViewLongGesture(_:)) )
  tableView.addGestureRecognizer(longGesture)
}
  
  
func handleTableViewLongGesture(sender: UILongPressGestureRecognizer) {
  let state = sender.state
  let location = sender.locationInView(tableView)
  guard let indexPath = tableView.indexPathForRowAtPoint(location) else {
    return
  }
    
    
    
  switch state {
  case .Began:
    sourceIndexPath = indexPath
    guard let cell = tableView.cellForRowAtIndexPath(indexPath) else {
      return
    }
    
    //Take a snapshot of the selected row using helper method.
    snapShot = customSnapShotFromView(cell)
    
    // Add the snapshot as subview, centered at cell's center...
    var center = CGPoint(x: cell.center.x, y: cell.center.y)
    snapShot?.center = center
    snapShot?.alpha = 0.0
    tableView.addSubview(snapShot!)
    UIView.animateWithDuration(0.25, animations: {
      // Offset for gesture location.
      center.y = location.y
      self.snapShot?.center = center
      self.snapShot?.transform = CGAffineTransformMakeScale(1.05, 1.05)
      self.snapShot?.alpha = 0.98
      
      cell.alpha = 0.0
      }, completion: { _ in
        cell.hidden = true
    })
  case .Changed:
    guard let snapShot = snapShot else {
      return
    }
    guard let sourceIndexPathTmp = sourceIndexPath else {
      return
    }
    var center = snapShot.center
    center.y = location.y
    snapShot.center = center
    
    // Is destination valid and is it different from source?
    if !indexPath.isEqual(sourceIndexPathTmp) {
      //self made exchange method
      objects.exchangeObjectAtIndex(indexPath.row, withObjectAtIndex: sourceIndexPathTmp.row)
      // ... move the rows.
      tableView.moveRowAtIndexPath(sourceIndexPathTmp, toIndexPath: indexPath)
      // ... and update source so it is in sync with UI changes.
      sourceIndexPath = indexPath
    }

  default:
    guard let sourceIndexPathTmp = sourceIndexPath else {
      return
    }
    guard let cell = tableView.cellForRowAtIndexPath(sourceIndexPathTmp) else {
      return
    }
    cell.hidden = false
    cell.alpha = 0.0
    
    UIView.animateWithDuration(0.25, animations: {
      self.snapShot?.center = cell.center
      self.snapShot?.transform = CGAffineTransformIdentity
      self.snapShot?.alpha = 0.0
      
      cell.alpha = 1.0
      }, completion: { _ in
        self.sourceIndexPath = nil
        self.snapShot?.removeFromSuperview()
        self.snapShot = nil
    })
  }
}
  
func customSnapShotFromView(inputView: UIView) -> UIImageView{
  // Make an image from the input view.
  UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0)
  inputView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
  let image = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
  
  let snapshot = UIImageView(image: image)
  snapshot.layer.masksToBounds = false
  snapshot.layer.cornerRadius = 0.0
  snapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
  snapshot.layer.shadowRadius = 5.0
  snapshot.layer.shadowOpacity = 0.4
  
  return snapshot
}

report a bug here:

this guard statement can cause the snapshot to freeze if you drag too far to cause the indexPath variable end up with a value of nil.
the solution is to move the statement into each of the Began and Changed case, like so:

1 Like

This tutorial is more than six months old so questions are no longer supported at the moment for it. We will update it as soon as possible. Thank you! :]