Dharmesh Kheni
Dharmesh Kheni

Reputation: 71854

How to drag a static cell into tableView swift?

I have one tableView in my storyBoard where I added 4 static cell into it and my storyBoard look like:

enter image description here

I don't have any dataSource for this tableView because my cells are static.

And I use below code to drag a cell and it is working fine till I scroll a table.

import UIKit

class TableViewController: UITableViewController {

    var sourceIndexPath: NSIndexPath = NSIndexPath()
    var snapshot: UIView = UIView()
    let longPress: UILongPressGestureRecognizer = {
        let recognizer = UILongPressGestureRecognizer()
        return recognizer
        }()

    override func viewDidLoad() {
        super.viewDidLoad()

        longPress.addTarget(self, action: "longPressGestureRecognized:")
        self.tableView.addGestureRecognizer(longPress)

        self.tableView.allowsSelection = false
    }

    override func viewWillAppear(animated: Bool) {

        self.tableView.reloadData()
    }

    // MARK: UIGestureRecognizer
    func longPressGestureRecognized(gesture: UILongPressGestureRecognizer){

        let state: UIGestureRecognizerState = gesture.state
        let location:CGPoint = gesture.locationInView(self.tableView)
        if let indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(location){

            switch(state){

            case UIGestureRecognizerState.Began:
                sourceIndexPath = indexPath

                let cell: UITableViewCell = self.tableView .cellForRowAtIndexPath(indexPath)!

                //take a snapshot of the selected row using helper method
                snapshot = customSnapshotFromView(cell)

                //add snapshot as subview, centered at cell's center
                var center: CGPoint = cell.center
                snapshot.center = center
                snapshot.alpha  = 0.0
                self.tableView.addSubview(snapshot)
                UIView.animateWithDuration(0.25, animations: { () -> Void in
                    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: { (finished) in
                        cell.hidden = true
                })

            case UIGestureRecognizerState.Changed:
                let cell: UITableViewCell = self.tableView.cellForRowAtIndexPath(indexPath)!

                var center: CGPoint = snapshot.center
                center.y = location.y
                snapshot.center = center
                print("location \(location.y)")

                //is destination valid and is it different form source?
                if indexPath != sourceIndexPath{
                    //update data source
                    //I have commented this part because I am not using any dataSource.
//                    self.customArray.exchangeObjectAtIndex(indexPath.row, withObjectAtIndex: sourceIndexPath.row)
                    //move the row
                    self.tableView.moveRowAtIndexPath(sourceIndexPath, toIndexPath: indexPath)
                    //and update source so it is in sync with UI changes
                    sourceIndexPath = indexPath
                }

                if (location.y < 68) || (location.y > 450) {
                    print("cancelled")
                    self.snapshot.alpha = 0.0
                    cell.hidden = false
                    UIView.animateWithDuration(0.10, animations: { () -> Void in

                        self.snapshot.center = cell.center
                        self.snapshot.transform = CGAffineTransformIdentity
                        self.snapshot.alpha = 0.0
                        //undo fade out
                        cell.alpha = 1.0

                        }, completion: { (finished) in
                            self.snapshot.removeFromSuperview()
                    })
                }

            case UIGestureRecognizerState.Ended:
                //clean up
                print("ended")
                let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
                cell.hidden = false

                UIView.animateWithDuration(0.25, animations: { () -> Void in

                    self.snapshot.center = cell.center
                    self.snapshot.transform = CGAffineTransformIdentity
                    self.snapshot.alpha = 0.0
                    //undo fade out
                    cell.alpha = 1.0

                    }, completion: { (finished) in
                        self.snapshot.removeFromSuperview()
                })
                break

            default:
                break
            }
        }else{
            gesture.cancelsTouchesInView = true
        }
    }

    func customSnapshotFromView(inputView: UIView) -> UIView {

        // Make an image from the input view.
        UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0)
        inputView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext();

        // Create an image view.
        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
    }

}

When I scroll after dragging it looks like:

enter image description here

As you can see cell is not appearing again. I want to drag and drop static cell and I want to save it's position so I will not rearrange again when I scroll.

Sample project for more Info.

This is just a demo project But I have added many elements into my cell and every cell have different UI.

Upvotes: 6

Views: 1993

Answers (4)

Abhishek
Abhishek

Reputation: 509

You can drag uitableview cell from uitableview delegates ....... 1) set the table view editing style to none in its delegate.

2) implement table view delegate to enable dragging of cell i.e canMoveRowAtIndexPath methods...

Upvotes: 2

Mikael Hellman
Mikael Hellman

Reputation: 2724

Are you doing this for layout purposes only, maybe a UICollectionView or a custom made UIScrollView could do the job?


Never the less, I have a solution:

  1. Create a IBOutlet collection holding all your static UITableViewCells
  2. Create a index list to simulate a "data source"
  3. Override the cellForRowAtIndexPath to draw using your own index list
  4. When updating the list order, update the indexList so that the view "remembers" this change

This Table view controller explains it all:

class TableViewController: UITableViewController {

  @IBOutlet var outletCells: [UITableViewCell]!
  var indexList = [Int]()

  override func viewDidLoad() {
    super.viewDidLoad()
    // Prepare a index list.
    // We will move positions in this list instead
    // of trying to move the view's postions.
    for (index, _) in outletCells.enumerate() {
      indexList.append(index)
    }
  }

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // Use dynamic count, not needed I guess but
    // feels better this way.
    return outletCells.count
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Use the index path to get the true index and return
    // the view on that index in our IBOutlet collection
    let realIndexForPos = indexList[indexPath.row]
    return outletCells[realIndexForPos]
  }

  @IBAction func onTap(sender: AnyObject) {
    // Simulating your drag n drop stuff here... :)
    let swapThis = 1
    let swapThat = 2

    tableView.moveRowAtIndexPath(NSIndexPath(forItem: swapThis, inSection: 0), toIndexPath: NSIndexPath(forItem: swapThat, inSection: 0))

    // Update the indexList as this is the "data source"
    // Do no use moveRowAtIndexPath as this is never triggred
    // This one line works: swap(&indexList[swapThis], &indexList[swapThat])
    // But bellow is easier to understand
    let tmpVal = indexList[swapThis]
    indexList[swapThis] = indexList[swapThat]
    indexList[swapThat] = tmpVal
  }
}

To create the IBOutlet use the Interface Builder. Use the Referencing Outlet Collection on each Table View Cell and drag, for each, to the same @IBOutlet in your controller code.

enter image description here

Upvotes: 1

zurakach
zurakach

Reputation: 1394

You can create multiple dynamic cells. You'll just have to dequeue cells with correct identifier.

Upvotes: 1

Mika
Mika

Reputation: 5845

There is a library that does exactly what you are looking to do with a very similar approach. It's called FMMoveTableView but it's for cells with a datasource.

I think that what is causing your problem is that when you move the cells around and then you scroll the datasource from the storyboard is no longer in sync with the table and therefore your cell object can't be redrawn.

I think you should implement your table this way:

  1. Make your 4 cells custom cells.
  2. Subclass each one.
  3. Create an Array with numbers 1 to 4
  4. Reorder the array on long drag
  5. Override cellForRowAtIndexPath to show the right cell for the right number

Upvotes: 2

Related Questions