This example shows one possible way of implementing Drag and Drop for tableviews using bindings and core data. Our purpose is to provide a simple UI for adding members from a pool of all people into a club. The focus of this example is the NSObject subclass named DragSupportDataSource. All of the table views in the application UI are bound to an array controller but have their data source set to a single DragSupportDataSource.

NSTableView drag and drop methods are called on the table view’s datasource. Using infoForBinding API, the DragSupportDataSource can find out which arraycontroller the table view in the drag operation is bound to. Once the destination array controller is found, it’s simple to perform the correct operations.

The data source methods implemented by the DragSupportDataSource return None/0 so that the normal bindings machinery will populate the table view with data. This may seem like a waste, but is a simple way of letting the DragSupportDataSource do the work of registering the table views for dragging. See DragSupportDataSource.py for more information.

Things to keep in mind:

  • The drag and drop implementation assumes all controllers are working with the same NSManagedObjectContext
  • Most of the code in the DragSupportDataSource is for error checking and un/packing objects



import Cocoa
import CoreData
import objc

class DragAppAppDelegate (Cocoa.NSObject):
    clubWindow = objc.IBOutlet()
    peopleWindow = objc.IBOutlet()

    _managedObjectModel = objc.ivar()
    _managedObjectContext = objc.ivar()
    _things = objc.ivar()

    def managedObjectModel(self):
        if self._managedObjectModel is None:
            allBundles = Cocoa.NSMutableSet.alloc().init()

            self._managedObjectModel = CoreData.NSManagedObjectModel.mergedModelFromBundles_(allBundles.allObjects())

        return self._managedObjectModel

    # Change this path/code to point to your App's data store.
    def applicationSupportFolder(self):
        paths = Cocoa.NSSearchPathForDirectoriesInDomains(Cocoa.NSApplicationSupportDirectory, Cocoa.NSUserDomainMask, True)

        if len(paths) == 0:
            Cocoa.NSRunAlertPanel("Alert", "Can't find application support folder",
                    "Quit", None, None)
            applicationSupportFolder = paths[0].stringByAppendingPathComponent_("DragApp")

        return applicationSupportFolder

    def managedObjectContext(self):
        if self._managedObjectContext is None:
            fileManager = Cocoa.NSFileManager.defaultManager()
            applicationSupportFolder = self.applicationSupportFolder()

            if not fileManager.fileExistsAtPath_isDirectory_(applicationSupportFolder, None)[0]:
                        applicationSupportFolder, None)

            url = Cocoa.NSURL.fileURLWithPath_(

            coordinator = CoreData.NSPersistentStoreCoordinator.alloc().initWithManagedObjectModel_(self.managedObjectModel())
            result, error = coordinator.addPersistentStoreWithType_configuration_URL_options_error_(CoreData.NSXMLStoreType, None, url, None, None)
            if result:
                self._managedObjectContext = CoreData.NSManagedObjectContext.alloc().init()

        return self._managedObjectContext

    def windowWillReturnUndoManager_(self, window):
        return self.managedObjectContext().undoManager()

    def saveAction_(self, sender):
        res, error = self.managedObjectContext().save_(None)
        if not res:

    def applicationShouldTerminate_(self, sender):
        context = self.managedObjectContext()

        reply = Cocoa.NSTerminateNow;

        if context is not None:
            if context.commitEditing():
                res, error = context.save_(None)
                if not res:
                    # This default error handling implementation should be
                    # changed to make sure the error presented includes
                    # application specific error recovery. For now, simply
                    # display 2 panels.
                    errorResult = Cocoa.NSApplication.sharedApplication().presentError_(error)

                    if errorResult: # Then the error was handled
                        reply = Cocoa.NSTerminateCancel
                        # Error handling wasn't implemented. Fall back to
                        # displaying a "quit anyway" panel.
                        alertReturn = Cocoa.NSRunAlertPanel(None, "Could not save changes while quitting. Quit anyway?" , "Quit anyway", "Cancel", None)
                        if alertReturn == Cocoa.NSAlertAlternateReturn:
                            reply = Cocoa.NSTerminateCancel;

                reply = Cocoa.NSTerminateCancel

        return reply


Abstract: Custom that handles Drag and Drop for table views by acting as a datasource.
import Cocoa
import objc
from objc import super

class DragSupportDataSource (Cocoa.NSObject):
    # all the table views for which self is the datasource
    registeredTableViews = objc.ivar()

    def init(self):
        self = super(DragSupportDataSource, self).init()
        if self is None:
            return None

        self.registeredTableViews = Cocoa.NSMutableSet.alloc().init()
        return self;

    # ******** table view data source necessities *********

    # We use this method as a way of registering for drag types for all
    # the table views that will depend on us to implement D&D. Instead of
    # setting up innumerable outlets, simply depend on the fact that every
    # table view will ask its datasource for number of rows.
    def numberOfRowsInTableView_(self, aTableView):
        # this is potentially slow if there are lots of table views
        if not self.registeredTableViews.containsObject_(aTableView):
            #Cache the table views that have "registered" with us.

        # return 0 so the table view will fall back to getting data from
        # its binding
        return 0

    def tableView_objectValueForTableColumn_row_(self, aView, aColumn, rowIdx):
        # return None so the table view will fall back to getting data from
        # its binding
        return None

    # put the managedobject's ID on the pasteboard as an URL
    def tableView_writeRowsWithIndexes_toPasteboard_(self, tv, rowIndexes, pboard):
        success = False

        infoForBinding = tv.infoForBinding_(Cocoa.NSContentBinding)
        if infoForBinding is not None:
            arrayController = infoForBinding.objectForKey_(Cocoa.NSObservedObjectKey)
            objects = arrayController.arrangedObjects().objectsAtIndexes_(

            objectIDs = Cocoa.NSMutableArray.array()
            for i in range(objects.count()):
                item = objects[i]
                objectID = item.objectID()
                representedURL = objectID.URIRepresentation()

            pboard.declareTypes_owner_([Cocoa.NSStringPboardType], None)
            pboard.addTypes_owner_([Cocoa.NSStringPboardType], None)
            success = pboard.setString_forType_(
                    objectIDs.componentsJoinedByString_(', '), Cocoa.NSStringPboardType)

        return success

    # *************** actual drag and drop work *****************
    def tableView_validateDrop_proposedRow_proposedDropOperation_(
            self, tableView, info, row, operation):

        # Avoid drag&drop on self. This might be interersting to enable in
        # light of ordered relationships
        if info.draggingSource() is not tableView:
            return Cocoa.NSDragOperationCopy
            return Cocoa.NSDragOperationNone

    def tableView_acceptDrop_row_dropOperation_(
            self, tableView, info, row, operation):

        success = False
        urlStrings = info.draggingPasteboard().stringForType_(Cocoa.NSStringPboardType)

        # get to the arraycontroller feeding the destination table view
        destinationContentBindingInfo = tableView.infoForBinding_(Cocoa.NSContentBinding)
        if destinationContentBindingInfo is not None:

            destinationArrayController = destinationContentBindingInfo.objectForKey_(Cocoa.NSObservedObjectKey)
            sourceArrayController = None

            # check for the arraycontroller feeding the source table view
            contentSetBindingInfo = destinationArrayController.infoForBinding_(Cocoa.NSContentSetBinding)
            if contentSetBindingInfo is not None:
                sourceArrayController = contentSetBindingInfo.objectForKey_(Cocoa.NSObservedObjectKey)

            # there should be exactly one item selected in the source controller, otherwise the destination controller won't be able to manipulate the relationship when we do addObject:
            if (sourceArrayController is not None) and (sourceArrayController.selectedObjects().count() == 1):
                context = destinationArrayController.managedObjectContext()
                destinationControllerEntity = Cocoa.NSEntityDescription.entityForName_inManagedObjectContext_(destinationArrayController.entityName(), context)

                items = urlStrings.split(', ')
                itemsToAdd = []

                for i in range(len(items)):
                    urlString = items[i]

                    # take the URL and get the managed object - assume
                    # all controllers using the same context
                    url = Cocoa.NSURL.URLWithString_(urlString)
                    objectID = context.persistentStoreCoordinator().managedObjectIDForURIRepresentation_(url)
                    if objectID is not None:
                        object = context.objectRegisteredForID_(objectID)

                        # make sure objects match the entity expected by
                        # the destination controller, and not already there
                        if object is not None and (object.entity() is destinationControllerEntity) and not (destinationArrayController.arrangedObjects().containsObject_(object)):

                if len(itemsToAdd) > 0:
                    success = True

        return success


from PyObjCTools import AppHelper

import DragAppAppDelegate
import DragSupportDataSource

if __name__ == "__main__":


Script for building the example.

    python3 setup.py py2app
from setuptools import setup