So adding drag & drop when the source and destination are my own app should be easy right?
Well, no. I did not find this easy at all. But I did get it to work :)
First things first: The apple documentation (that I could find) is of little to no help at all. It is either outdated or it laks guidance. Besides, I must be the only one having trouble with this because there is almost nothing to find on the inet ... well, there is, but it is also outdated...
Ok, here is the deal:
First decide which type to drag & drop. For that type the NSPasteboardWriting and NSPasteboardReading protocols have to be adopted. Oh, and that type (class) must inherit from NSObject as well.
Like this:
class MyData: NSObject, NSPasteboardWriting, NSPasteboardReading {
var eData: String!
var oData: String!
init(e: String, o: String) {
super.init() // Necessary for NSObject
oData = o
eData = e
}
private func jsonString() -> String {
let top = SwifterJSON.createJSONHierarchy()
top["eData"].stringValue = eData
top["oData"].stringValue = oData
return top.description
}
// MARK: - Pasteboard Writing Protocol
@objc func writableTypesForPasteboard(pasteboard: NSPasteboard!) -> [AnyObject]! {
return ["com.mycompany.MyApp.MyData"]
}
@objc func pasteboardPropertyListForType(type: String!) -> AnyObject! {
return jsonString()
}
// MARK: - Pasteboard Reading Protocol
init!(pasteboardPropertyList propertyList: AnyObject!, ofType type: String!) {
super.init()
if let jsonString = propertyList as? String {
let (topOrNil, errorOrNil) = SwifterJSON.createJsonHierarchyFromString(jsonString)
if let top = topOrNil {
oData = top["oData"].stringValue ?? "Error"
eData = top["eData"].stringValue ?? "Error"
} else {
return nil
}
} else {
return nil
}
}
static func readableTypesForPasteboard(pasteboard: NSPasteboard!) -> [AnyObject]! {
return ["com.mycompany.MyApp.MyData"]
}
static func readingOptionsForType(type: String!, pasteboard: NSPasteboard!) -> NSPasteboardReadingOptions {
return NSPasteboardReadingOptions.AsString
}
}
In the above code I used my own SwifterJSON framework to encode and decode the data of MyData. If you would like to take a look at that, find the link on the left hand side of this blog.
Normally the propertyList in the pasteboard reading protocol will deliver NSData objects, but since I know that there will be a JSON string in there, I have also supplied the optional readingOptionsForType method so that the propertyList will be readable as a String when the init is called.
PS I have ignored error handling in init for brevity.
With the above code in place, we can now create MyData objects from the pasteboard like this:
func performDragOperation(sender: NSDraggingInfo) -> Bool {
// Get the pasteboard
if let pboard = sender.draggingPasteboard() {
// Get the Y coordinate for the drop
let dropCoordinate = sender.draggingLocation()
// Check if the drop position is within any of our subviews.
if !pointIsInADocumentView(dropCoordinate) { return false }
// Offset the Y coordinate
let dropPoint = theView.convertPoint(dropCoordinate, toView: aSubView)
// Get the index for the drop
let dropIndex = lineIndexFromYCoordinate(dropPoint.y)
// Make sure that there are filenames in the pasteboard
if pboard.availableTypeFromArray([NSFilenamesPboardType]) == NSFilenamesPboardType {
// Get the filenames from the pasteboard
let files = filesFromPboard(pboard)
// Add the filenames to the datasource
insertLines(files, atIndex: dropIndex)
// Update the view
theView.needsDisplay = true
// The drop was accepted
return true
} else if pboard.availableTypeFromArray(["com.mycompany.MyApp.MyData"]) == "com.mycompany.MyApp.MyData" {
if var specs = pboard.readObjectsForClasses([MyData.self], options: nil) as? Array<MyData> {
dataModel.insertLines(fileSpecs, atIndex: dropIndex)
} else {
log.atLevelError(id: 0, source: "performDragOperation", message: "Could not read MyData from pasteboard")
}
// The drop was accepted
return true
}
}
// Still here, then something went wrong. The drop is not accepted.
return false
}
The yellowish underlaid code is the important bit. I have left some of the older code in so that you see how this could all work together. Refer to the blogs I mentioned at the top to see more on that. You can find a link to the logging framework I use to the left of this blog. (Note: I always accept the drop, this is my own data type, so I better make sure all errors are fixed before shipping!)
Oh, lest I forget, we also need to register ourselves to receive the new type of drop: in awakeFromNb I added the following:
// Add drag and drop support
registerForDraggedTypes([NSFilenamesPboardType, "com.mycompany.MyApp.MyData"])
class MyView: NSView, NSDraggingSource {...
func draggingSession(session: NSDraggingSession, sourceOperationMaskForDraggingContext context: NSDraggingContext) -> NSDragOperation {
switch context {
case .OutsideApplication: return .None
case .WithinApplication: return .Move
}
}
To begin the drag & drop I added the following code to the mouseDown method:
override func mouseDown(theEvent: NSEvent) {
// Get the mouse position from the event position
let mp = convertPoint(theEvent.locationInWindow, fromView: nil)
if (mp.x >= 0.0) && (mp.y >= 0.0) && (mp.x <= frame.width) && (mp.y <= frame.height) {
let mousePosition = CGPointMake(mp.x - MARGIN_BEFORE_LINE, mp.y)
// If the option key is down as well, this will be a drag & drop
if theEvent.modifierFlags.isSet(.AlternateKeyMask) {
// Find out if the mouse is down in a selection
let lineIndex = lineIndexFromYCoordinate(mousePosition.y)
if lineIndex >= dataSource.nofLines { return } // Ignore if the mouse down position is outside any of the lines
let line = dataSource.lineAtIndex(lineIndex)
let charIndex = line.indexForOffset(mousePosition.x)
let lines = dataSource.linesInBlokSelect(lineIndex, charIndex: charIndex, lineSource: dataSource.lineAtIndex)
var dragItems = Array<NSDraggingItem>()
for l in lines { dragItems.append(NSDraggingItem(pasteboardWriter: l)) }
beginDraggingSessionWithItems(dragItems, event: theEvent, source: self)
// Remove the original data from the datamodel
for l in lines { dataSource.removeLine(l) }
} else {
// Normal mouse down
mouseDownPosition = mousePosition
}
} else {
return
}
}
While the above should get you up and running, it is not complete. You will probably want to add visual indicators of your own making to the drag & drop. This can be done (so I understand) through the NSDraggingItem
Happy coding...
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
No comments:
Post a Comment