7
votes

So I have seen a lot of posts about reordering cells that pertain to using "edit mode", but none for the problem I have. (Excuse me if I am wrong).

I am building a ranking app, and looking for a way to use a long gesture recognizer to reorder the cells in my UITableView. Essentially a user will be able to reorder and "Rank" the cells full of strings in a group with their friends.

I would go the standard route of using an "edit" bar button item in the nav bar, but I am using the top right of the nav bar for adding new strings to the tableview already. (The following image depicts what I mean).

So far, I have added `

    var lpgr = UILongPressGestureRecognizer(target: self, action: "longPressDetected:")

    lpgr.minimumPressDuration = 1.0;
    tableView.addGestureRecognizer(lpgr)`

to my viewDidLoad method, and started creating the following function:

    func longPressDetected(sender: AnyObject) {

    var longPress:UILongPressGestureRecognizer = sender as UILongPressGestureRecognizer
    var state:UIGestureRecognizerState = longPress.state

    let location:CGPoint = longPress.locationInView(self.tableView) as CGPoint
    var indexPath = self.tableView.indexPathForRowAtPoint(location)?

    var snapshot:UIView!
    var sourceIndexPath:NSIndexPath!

}

All of the resources I have scowered for on the internet end up showing me a HUGE, LONG list of additives to that function in order to get the desired result, but those examples involve core data. It seems to me that there must be a far easier way to simply reorder tableview cells with a long press?

enter image description here

3
Yeah, you definitely don't need to use core data. You just want to drag and drop to reorder cells? Duplicate the tableview cell as a draggable view upon gesture recognition, then remove that view and update the table contents accordingly once that gesture is over.Lyndsey Scott

3 Answers

22
votes

Dave's answer is great. Here is the swift 4 version of this tutorial:

WayPointCell is your CustomUITableViewCell and wayPoints is the dataSource array for the UITableView

First, put this in your viewDidLoad, like Alfi mentionend:

override func viewDidLoad() {
    super.viewDidLoad()

    let longpress = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureRecognized(gestureRecognizer:)))
    self.tableView.addGestureRecognizer(longpress)
}

Then implement the method:

func longPressGestureRecognized(gestureRecognizer: UIGestureRecognizer) {

    let longpress = gestureRecognizer as! UILongPressGestureRecognizer
    let state = longpress.state
    let locationInView = longpress.location(in: self.tableView)
    var indexPath = self.tableView.indexPathForRow(at: locationInView)

    switch state {
    case .began:
        if indexPath != nil {
            Path.initialIndexPath = indexPath
            let cell = self.tableView.cellForRow(at: indexPath!) as! WayPointCell
            My.cellSnapShot = snapshopOfCell(inputView: cell)
            var center = cell.center
            My.cellSnapShot?.center = center
            My.cellSnapShot?.alpha = 0.0
            self.tableView.addSubview(My.cellSnapShot!)

            UIView.animate(withDuration: 0.25, animations: {
                center.y = locationInView.y
                My.cellSnapShot?.center = center
                My.cellSnapShot?.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
                My.cellSnapShot?.alpha = 0.98
                cell.alpha = 0.0
            }, completion: { (finished) -> Void in
                if finished {
                    cell.isHidden = true
                }
            })
        }

    case .changed:
        var center = My.cellSnapShot?.center
        center?.y = locationInView.y
        My.cellSnapShot?.center = center!
        if ((indexPath != nil) && (indexPath != Path.initialIndexPath)) {

            self.wayPoints.swapAt((indexPath?.row)!, (Path.initialIndexPath?.row)!)
            //swap(&self.wayPoints[(indexPath?.row)!], &self.wayPoints[(Path.initialIndexPath?.row)!])
            self.tableView.moveRow(at: Path.initialIndexPath!, to: indexPath!)
            Path.initialIndexPath = indexPath
        }

    default:
        let cell = self.tableView.cellForRow(at: Path.initialIndexPath!) as! WayPointCell
        cell.isHidden = false
        cell.alpha = 0.0
        UIView.animate(withDuration: 0.25, animations: {
            My.cellSnapShot?.center = cell.center
            My.cellSnapShot?.transform = .identity
            My.cellSnapShot?.alpha = 0.0
            cell.alpha = 1.0
        }, completion: { (finished) -> Void in
            if finished {
                Path.initialIndexPath = nil
                My.cellSnapShot?.removeFromSuperview()
                My.cellSnapShot = nil
            }
        })
    }
}

func snapshopOfCell(inputView: UIView) -> UIView {

    UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
    inputView.layer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    let cellSnapshot : UIView = UIImageView(image: image)
    cellSnapshot.layer.masksToBounds = false
    cellSnapshot.layer.cornerRadius = 0.0
    cellSnapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
    cellSnapshot.layer.shadowRadius = 5.0
    cellSnapshot.layer.shadowOpacity = 0.4
    return cellSnapshot
}

struct My {
    static var cellSnapShot: UIView? = nil
}

struct Path {
    static var initialIndexPath: IndexPath? = nil
}
9
votes

Give this tutorial a shot, you'll likely be up and running within 20 minutes:

Great Swift Drag & Drop tutorial

It's easy. I've only been developing for 3 months and I was able to implement this. I also tried several others and this was the one I could understand.

It's written in Swift and it's practically cut and paste. You add the longPress code to your viewDidLoad and then paste the function into the 'body' of your class. The tutorial will guide you but there's not much more to it.

Quick explanation of the code: This method uses a switch statement to detect whether the longPress just began, changed, or is in default. Different code runs for each case. It takes a snapshot/picture of your long-pressed cell, hides your cell, and moves the snapshot around. When you finished, it unhides your cell and removes the snapshot from the view.

Warning: My one word of caution is that although this drag/drop looks great and works close to perfectly, there does seem to be an issue where it crashes upon dragging the cell below the lowest/bottom cell.

Drag & Drop Crash Problem

1
votes

Since iOS 11 this can be achieved by implementing the built in UITableView drag and drop delegates.

You will find a detailed description of how to implement them in this answer to a similar question: https://stackoverflow.com/a/57225766/10060753