7

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

CL8989
  • 155
  • 2
  • 2
  • 6
  • 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 Dec 24 '14 at 03:40
  • Possible duplicate of [Using long press gesture to reorder cells in tableview?](https://stackoverflow.com/questions/12257008/using-long-press-gesture-to-reorder-cells-in-tableview) – Grant Kamin Aug 06 '19 at 14:16

3 Answers3

23

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
}
Teetz
  • 3,475
  • 3
  • 21
  • 34
  • 1
    Don't forget to add these two line to ViewDidLoad: let longpress = UILongPressGestureRecognizer(target: self, action: "longPressGestureRecognized:") tableView.addGestureRecognizer(longpress) – Ahmadreza Nov 27 '17 at 04:13
  • 4
    how about scrolling tableView when the longPressGestureRecognizer hits the bottom of the screen? – alionthego Jan 19 '18 at 18:13
  • I had to change longPressGestureRecognized to longPressGestureRecognizer and also replace func longPressGestureRecognizer(gestureRecognizer: UIGestureRecognizer) with @objc func longPressGestureRecognizer(gestureRecognizer: UIGestureRecognizer) –  Apr 21 '18 at 23:45
  • Working great..!! – Mitesh Dobareeya Jun 02 '18 at 07:10
  • This is pretty good, though if you drag a table row down below the last row, it crashes because `indexPath` is `nil` inside the `default` switch case. – Clifton Labrum Jul 02 '18 at 20:35
  • 1
    @CliftonLabrum See this SO-post for that issue: https://stackoverflow.com/questions/32256784/uitableview-drag-drop-outside-table-crash – Teetz Jul 03 '18 at 05:11
  • 1
    This solution worked! But I get crash: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (9) must be equal to the number of rows contained in that section before the update (9), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 1 moved out).' When move a cell to another tableview section. How do I fix this? – fphelp Jan 09 '19 at 07:15
  • 1
    @alionthego i have developed same thing in expand collapse UITableview, but I am unable to add scrolling at the end of UITable how to do it – A.s.ALI Jan 09 '19 at 10:25
  • This is fkin amazing, everything I needed nicely packed into this answer, thank you! – Starwave May 23 '19 at 20:52
  • Can you help out on this: https://stackoverflow.com/questions/72259537/drag-drop-table-view-cell-has-weired-buggy-animation-while-dragging-cell-usin – Kishan Bhatiya May 17 '22 at 06:21
  • Can't understand why it has so weird behaviour, when I remove your structs My and Path and try to change them to simple vars, I've got crash. – Alexander Yakovlev May 08 '23 at 09:33
9

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

Community
  • 1
  • 1
Dave G
  • 12,042
  • 7
  • 57
  • 83
  • 3
    This is the first tutorial I've seen for this that basically just 'works'. Thanks for sharing! – Andrew Varvel Sep 16 '15 at 06:41
  • Thanks! It worked. Question: I added one more state "ended" for longPressEvent and after that it stopped working...like cellSnapShot overlapping other rows etc. Basically I was trying to get end of LongPressGesture to update my DB. – Mohit Kumar May 21 '21 at 09:16
  • can you help out on this: https://stackoverflow.com/questions/72259537/drag-drop-table-view-cell-has-weired-buggy-animation-while-dragging-cell-usin/72265135#72265135 – Kishan Bhatiya May 17 '22 at 04:58
  • Why are you using structs such as My and Path and why it's not working without it? – Alexander Yakovlev May 08 '23 at 09:35
1

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

mmklug
  • 2,252
  • 2
  • 16
  • 31