Drag and Drop Tutorial for macOS
The drag-and-drop mechanism has always been an integral part of Macs. Learn how to adopt it in your apps with this drag and drop tutorial for macOS. By Warren Burton.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Drag and Drop Tutorial for macOS
30 mins
- Getting Started
- Meet the Project App
- Pasteboards and Dragging Sessions
- Creating a Dragging Destination
- Handling an Exit
- Tell the User What’s Happening
- Wrap up the Drag
- Use DestinationView’s Data
- Creating a Dragging Source
- Supplying a Standard Dragging Type
- Start a Dragging Session
- Take the TIFF
- Unarchive the Image Data
- Show me the Image Data!
- Dragging Custom Types
- Create the Dragging Source
- Accept the New Type
- Handle the Action Instruction
- Where to go From Here?
Create the Dragging Source
Open AppActionSourceView.swift. It’s mostly empty except for this important definition:
enum SparkleDrag {
static let type = "com.razeware.StickerDrag.AppAction"
static let action = "make sparkles"
}
This defines your custom dragging type and action identifier.
Dragging source types must be Uniform Type Identifiers. These are reverse-coded name paths that describe a data type.
For example, if you print out the value of kUTTypeTIFF
you’ll see that it is the string public.tiff.
To avoid a collision with an existing type, you can define the identifier like this: bundle identifier + AppAction. It is an arbitrary value, but you keep it under the private namespace of the application to minimize the risk of using an existing name.
If you attempt to construct a NSPasteboardItem
with a type that isn’t UTI, the operation will fail.
Now you need to make AppActionSourceView
adopt NSDraggingSource
. Open AppActionSourceView.swift and add the following extension:
// MARK: - NSDraggingSource
extension AppActionSourceView: NSDraggingSource {
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor
context: NSDraggingContext) -> NSDragOperation {
switch(context) {
case .outsideApplication:
return NSDragOperation()
case .withinApplication:
return .generic
}
}
}
This code block differs from ImageSourceView
because you’ll place private data on the pasteboard that has no meaning outside the app. That’s why you’re using the context
parameter to return a NSDragOperation()
when the mouse is dragged outside your application.
You’re already familiar with the next step. You need to override the mouseDown(with:)
event to start a dragging session with a pasteboard item.
Add the following code into the AppActionSourceView
class implementation:
override func mouseDown(with theEvent: NSEvent) {
let pasteboardItem = NSPasteboardItem()
pasteboardItem.setString(SparkleDrag.action, forType: SparkleDrag.type)
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(self.bounds, contents:snapshot())
beginDraggingSession(with: [draggingItem], event: theEvent, source: self)
}
What did you do in there?
You constructed a pasteboard item and placed the data directly inside it for your custom type. In this case, the data is a custom action identifier that the receiving view may use to make a decision.
You can see how this differs from ImageSourceView
in one way. Instead of deferring data generation to the point when the view accepts the drop with the NSPasteboardItemDataProvider
protocol, the dragged data goes directly to the pasteboard.
Why would you use the NSPasteboardItemDataProvider
protocol? Because you want things to move as fast as possible when you start the drag session in mouseDown(with:)
.
If the data you’re moving takes too long to construct on the pasteboard, it’ll jam up the main thread and frustrate users with a perceptible delay when they start dragging.
In this case, you place a small string on the pasteboard so that it can do it right away.
Accept the New Type
Next, you have to let the destination view accept this new type. By now, you already know how to do it.
Open DestinationView.swift and add SparkleDrag.type
to the registered types. Replace the following line:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF)] }
With this:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF),SparkleDrag.type] }
Now SparkleDrags are acceptable!
performDragOperation(:_)
needs a new else-if
clause, so add this code at the end of the method just before return false
:
else if let types = pasteBoard.types, types.contains(SparkleDrag.type),
let action = pasteBoard.string(forType: SparkleDrag.type) {
delegate?.processAction(action, center:point)
return true
}
This addition extracts the string from the pasteboard. If it corresponds to your custom type, you pass the action back to the delegate.
You’re almost done, you just need to update StickerBoardViewController
to deal with the action instruction.
Handle the Action Instruction
Open StickerBoardViewController.swift and replace processAction(_:center:)
with this:
func processAction(_ action: String, center: NSPoint) {
//1.
if action == SparkleDrag.action {
invitationLabel.isHidden = true
//2.
if let image = NSImage(named:"star") {
//3.
for _ in 1..<Appearance.numStars {
//A.
let maxSize:CGFloat = Appearance.maxStarSize
let sizeChange = CGFloat(arc4random_uniform(Appearance.randonStarSizeChange))
let finalSize = maxSize - sizeChange
let newCenter = center.addRandomNoise(Appearance.randomNoiseStar)
//B.
let imageFrame = NSRect(x: newCenter.x, y: newCenter.y, width: finalSize , height: finalSize)
let imageView = NSImageView(frame:imageFrame)
//C.
let newImage = image.tintedImageWithColor(NSColor.randomColor())
//D.
imageView.image = newImage
targetLayer.addSubview(imageView)
}
}
}
}
The above code does the following:
- Only responds to the known sparkle action
- Loads a star image from the bundle
- Makes some copies of the star image and…
- Generates some random numbers to alter the star position.
- Creates an
NSImageView
and sets its frame. - Gives the image a random color -- unless you're going for a David Bowie tribute, black stars are a bit gothic.
- Places the image on the view.
- Generates some random numbers to alter the star position.
- Creates an
NSImageView
and sets its frame. - Gives the image a random color -- unless you're going for a David Bowie tribute, black stars are a bit gothic.
- Places the image on the view.
Build and run. Now you can drag from the sparkles view onto the sticker view to add a spray of stars to your view.
Where to go From Here?
Congratulations, you created a custom drag and drop interface in your very own app!
You can use the Save Image To Desktop button to save your image as a JPG with the name StickerDrag. Maybe take it a step further and tweet it to the team @rwenderlich.
Here's the source code for the the completed project.
This drag and drop tutorial for macOS covered the basics of the Cocoa drag and drop mechanism, including:
- Creating a dragging destination and accepting several different types of data
- Using the dragging session lifecycle to provide user feedback of the drag operation
- Decoding information from the pasteboard
- Creating a dragging source and providing deferred data
- Creating a dragging source that provides a custom data type
Now you have the knowledge and experience needed to support drag and drop in any macOS app.
There's certainly more to learn.
You could study up on how to apply effects, such as changing the dragging image during the drag or implementing an animated drop transition, or working with promised files -- Photos is one application that places promised data on the dragging pasteboard.
Another interesting topic is how to use drag and drop with NSTableView
and NSOutlineView
, which work in slightly different ways. Learn about it from the following resources:
- Apple's Drag and Drop Programming Topics
- Apple's Sandboxing and Security Scoped Data, where you'll find information about how dragging and dropping files works if an application is sandboxed.
- CocoaDragAndDrop sample code (in Objective-C)
- DragNDropOutlineView sample code (in Objective-C)
If you have any questions or comments about this drag and drop tutorial for macOS, please join the discussion below! And remember, sometimes life is a dragging experience, but everything's better with unicorns and sparkles. :]