This is a solution that is a re-working of a post by Scott Sherwood which can be found here.

I hope that I have simplified the process involved in providing a complete Swift class that you can implement in your project. I have also added the ability to determine which cell an object was dropped onto, allowing for a 're-ordering' functionality.

Pre-requisites:

  • A core data model which includes a double called 'order'.
  • The CoreDataConduit class from this blog post.
  • The NSFetchedResultsControllerDelegate methods for listening to changes in content as described here.

Here is the class (and it includes a delegate protocol at the top).

import UIKit

protocol DragDropControllerDelegate : UIGestureRecognizerDelegate {

    func droppedOn(droppedView:UIView, withContent theContent:AnyObject?, atIndex indexPath:NSIndexPath?, fromThisTable fromTable:UITableView, withIndexPath fromIndexPath:NSIndexPath)
    func startedDraggingFrom(tableView:UITableView, withIndexPath indexPath:NSIndexPath)
    func returnContentFor(tableView:UITableView, withIndexPath indexPath:NSIndexPath)->AnyObject?
}

class DragDropController: NSObject, UIGestureRecognizerDelegate{

    var myLongPressRecognizerArray = [UILongPressGestureRecognizer()]
    var myLongPressRecognizer:UILongPressGestureRecognizer{

        if let lastView = self.myLongPressRecognizerArray.last?.view {

            var newRecogniser = UILongPressGestureRecognizer()
            self.myLongPressRecognizerArray.append(newRecogniser)

            return newRecogniser
        }else if self.myLongPressRecognizerArray.count == 0{

            var newRecogniser = UILongPressGestureRecognizer()
            self.myLongPressRecognizerArray.append(newRecogniser)

            return newRecogniser
        }

        return myLongPressRecognizerArray.last!
    }

    var myMainView:UIView?
    var myTableView:UITableView?
    var myDraggingCellIndex:NSIndexPath?

    var myDelegate:DragDropControllerDelegate?
    var myDraggingView:UIView?
    var myDraggingContent:AnyObject?
    var enabled:Bool = false{

        didSet(newValue){

            if newValue == false {

                self.cancelDragging()
            }
        }
    }

    class func debug(string:String){

        println("DragDropController: \(string)")
    }

    func applyGestureToView(theView:UIView){

        if theView.gestureRecognizers?.count > 0 {

            DragDropController.debug("Gesture recogniser already exists on this view.")
        }else{
            DragDropController.debug("Applying gesture recogniser.")
            let theLastLongPressRecogniser = self.myLongPressRecognizer
            theLastLongPressRecogniser.delegate = self
            theView.addGestureRecognizer(theLastLongPressRecogniser)
        }
    }

    func longGestureAction(gesture:UILongPressGestureRecognizer){

        switch gesture.state {

        case UIGestureRecognizerState.Began:

            if enabled == true {

                DragDropController.debug("longGestureAction did begin")

                if let cell = gesture.view as? UITableViewCell {

                    self.myTableView?.scrollEnabled = false
                    if let ip = self.myTableView?.indexPathForCell(cell){
                        self.myDelegate?.startedDraggingFrom(self.myTableView!, withIndexPath: ip)
                        self.myDraggingContent = self.myDelegate?.returnContentFor(self.myTableView!, withIndexPath: ip)
                        self.myDraggingCellIndex = ip
                    }

                    UIGraphicsBeginImageContext(cell.contentView.bounds.size)
                    cell.contentView.layer.renderInContext(UIGraphicsGetCurrentContext())
                    if let image = UIGraphicsGetImageFromCurrentImageContext() {
                        self.myDraggingView = UIImageView(frame: cell.contentView.bounds)
                        if let myDraggingImage = self.myDraggingView as? UIImageView {

                            DragDropController.debug("Creating dragging subview")

                            myDraggingImage.image = image
                            myDraggingImage.layer.cornerRadius = 5.0
                            myDraggingImage.layer.backgroundColor = UIColor.orangeColor().CGColor
                            self.myDraggingView?.center = gesture.locationInView(self.myMainView)
                            self.myMainView?.addSubview(myDraggingImage)
                        }
                    }
                }
            }

        case UIGestureRecognizerState.Changed:

            self.myDraggingView?.center = gesture.locationInView(self.myMainView)

        case UIGestureRecognizerState.Ended:

            DragDropController.debug("longGestureAction did end")

            let viewHit = self.myMainView?.hitTest(gesture.locationInView(self.myMainView), withEvent: nil)

            var theSuperView = viewHit?.superview

            if viewHit?.isKindOfClass(UITableView.classForCoder()) == true {

                theSuperView = viewHit
            }else{

                while (theSuperView != nil && theSuperView?.isKindOfClass(UITableView.classForCoder()) == false) {

                    DragDropController.debug("Found superView which is not UITableView")

                    theSuperView = theSuperView?.superview
                }
            }

            if let theTargetTable = theSuperView as? UITableView {

                DragDropController.debug("Found target table")


                if let theTargetCell = viewHit?.superview as? UITableViewCell {

                    DragDropController.debug("Fount target cell")

                    let theCellIndex = theTargetTable.indexPathForCell(theTargetCell)

                    if self.myDraggingCellIndex != nil && self.myTableView != nil{

                        DragDropController.debug("Dropped on theTargetTable at theCellIndex: \(theCellIndex)")

                        self.myDelegate?.droppedOn(theTargetTable, withContent: self.myDraggingContent, atIndex:theCellIndex, fromThisTable:self.myTableView!, withIndexPath:self.myDraggingCellIndex!)
                    }
                }else {

                    if self.myDraggingCellIndex != nil && self.myTableView != nil{
                        DragDropController.debug("Dropped on theTargetTable with no cell in particular.")

                        self.myDelegate?.droppedOn(theTargetTable, withContent: self.myDraggingContent, atIndex:nil, fromThisTable:self.myTableView!, withIndexPath:self.myDraggingCellIndex!)
                    }
                }

                self.myTableView?.scrollEnabled = true
            }else{

                if self.myDraggingCellIndex != nil && self.myTableView != nil && viewHit != nil{
                    DragDropController.debug("Dropped on header view.")

                    self.myDelegate?.droppedOn(viewHit!, withContent: self.myDraggingContent, atIndex:nil, fromThisTable:self.myTableView!, withIndexPath:self.myDraggingCellIndex!)
                }
            }

            fallthrough

        case UIGestureRecognizerState.Cancelled:
            fallthrough
        case UIGestureRecognizerState.Failed:
            fallthrough
        case UIGestureRecognizerState.Possible:
            self.cancelDragging()
        }
    }

    func cancelDragging(){

        DragDropController.debug("cancelDragging")

        self.myTableView?.scrollEnabled = true
        self.myDraggingContent = nil
        self.myDraggingView?.removeFromSuperview()
    }

    //GESTURE RECOGNIZER FUNCTIONSS

    func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {

        DragDropController.debug("gestureRecognizerShouldBegin: \(gestureRecognizer)")

        if let longPress = gestureRecognizer as? UILongPressGestureRecognizer {

            longPress.addTarget(self, action: "longGestureAction:")
        }

        return true
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {

        return true
    }
}

So how do you use this in conjunction with an NSFetchedResultsController? Whichever class has the capability to modify the CoreData records that make up your tableViews should implement the DragDropControllerDelegate protocol and implement the methods below.

To make sure that the effect of a drag/drop action is visible, you should make sure that you have implemented the appropriate NSFetchedResultsControllerDelegate methods. Alternatively, just call yourTable.reloadTable() at the end of the 'droppedon' function.

func droppedOn(droppedView:UIView, withContent theContent:AnyObject?, atIndex indexPath:NSIndexPath?, fromThisTable fromTable:UITableView, withIndexPath fromIndexPath:NSIndexPath) {

        var theObject:YourDataModelName? = nil

        if let theObjectId = theContent as? NSManagedObjectID {

            if let theRetrievedObject = CoreDataConduit.getObjectFromId(theObjectId) as? YourDataModelName {

                theObject = theRetrievedObject
            }
        }else if let theRetrievedObject = theContent as? YourDataModelName {

            theObject = theRetrievedObject
        }else{

            if let theRetrievedObject = yourFetchedResultsController?.returnObjectFromIndex(fromIndexPath) as? YourDataModelName{

                theObject = theRetrievedObject
            }
        }

        var newOrderDouble:NSNumber? = nil

        var targetTable = fromTable

        if indexPath != nil {
            if let tableView = droppedView as? UITableView {
                if let newOrderObject = self.getControllerForTableView(tableView)?.returnObjectFromIndex(indexPath!) as? YourDataModelName {

                    newOrderDouble = newOrderObject.order.doubleValue + 0.00001

                    targetTable = tableView
                }
            }
        }else if let tableView = droppedView as? UITableView {
            newOrderDouble = self.getHighestOrderNumber(self.getControllerForTableView(tableView)?.myController)
            targetTable = tableView

        }

        if theObject != nil && newOrderDouble != nil{

            if targetTable == self.someTable {

               theObject?.order = newOrderDouble!

               CoreDataConduit.saveContext(theContext: theObject?.managedObjectContext)

            }else if targetTable == self.anotherTable{

               theObject?.order = newOrderDouble!

               CoreDataConduit.saveContext(theContext: theObject?.managedObjectContext)
                }
            }
        }
    }    

    func returnContentFor(tableView: UITableView, withIndexPath indexPath: NSIndexPath)->AnyObject? {
        //YOU MAY WANT TO PASS SOME DATA TO THE DRAGGER TO MAKE AVAILABLE IN YOUR 'DROPPEDON' FUNCTION
    }

    func startedDraggingFrom(tableView: UITableView, withIndexPath indexPath: NSIndexPath) {

        //YOU MAY WANT TO ADD SOME FUNCTIONALITY AT THE START OF THE DRAGGING.
    }

    //HELPER FUNCITONS

    func returnThisIndex(theIndex:NSIndexPath?, fromThisFetchedResultsController fetchedResultsController:NSFetchedResultsController?)->AnyObject? {

        let controllerSections = fetchedResultsController?.sections?.count ?? 0
        let queryingSection = theIndex?.section ?? 0
        let queryingRow = theIndex?.row ?? 0

        if controllerSections > queryingSection {

            if let sectionInfo = fetchedResultsController?.sections?[queryingSection] as? NSFetchedResultsSectionInfo {

                if sectionInfo.numberOfObjects > queryingRow {

                    return fetchedResultsController?.objectAtIndexPath(theIndex!)
                }
            }
        }

        return nil
    }

    class func getHighestOrderNumber(existingFetchedResultsController:AnyObject?)->NSNumber{

        if let controller = existingFetchedResultsController as? NSFetchedResultsController {

            if let fetchedObjcts = controller.fetchedObjects{

                if let lastObject = fetchedObjcts.last as? YourDataModelName{

                    return (Float(lastObject.order) + 0.00001)
                }
            }

        }

        return 1.0
    }

I'm utilizing my CoreDataConduit class from this blog post so make sure it's included in your project.

The 'droppedon' delegate method is the action that is taken once the drag is finished and the object is dropped. The first stage in my implementation above is checking whether content has been passed to the function that is either an object ID (allowing me to retrieve the CoreData object) or a reference to the CoreData object itself. If it's neither of these - then I attempt to pull the CoreData object from the fetched results controller itself.

The next stage is getting the 'order'. I have assumed in this example that your CoreData object model includes a double called 'order' which determines the viewing order of each row in the table. The order is made to be just after the cell onto which the object was dropped, or the very end of the series if the object was dropped onto the empty space within the tableview.

The last stage is modifying the CoreData object. In this example, I have only changed the order, but you can include other changes here as well.

Please drop a comment if you are having any trouble and I'll do my best to help you out!

Jeremy