Skip to content

Instantly share code, notes, and snippets.

@jb-apps
Created August 29, 2023 17:27
Show Gist options
  • Save jb-apps/d8e772451f3b53ed982923786994ad90 to your computer and use it in GitHub Desktop.
Save jb-apps/d8e772451f3b53ed982923786994ad90 to your computer and use it in GitHub Desktop.
//
// ImageDraggableModifier.swift
// Pictura
//
// Created by Jonathan Benavides Vallejo on 25.08.23.
//
import SwiftUI
import AppKit
struct ImageDraggableModifier: ViewModifier {
/// URL to where the image is located
let fileURL: URL
/// Useful when dealing with big images, Pictura images are huge so the preview is a bit wonky, by using thumbnails everything looks smooth
var thumbnail: NSImage?
/// The drag operation to perform, i.e: .copy or .move
let dragOperation: NSDragOperation
/// The drag operation has completed, this does not mean the image has been dragged outside.
/// For example if you are performing a ".move" you must check if the image still exists.
var dragOperationCompleted: (() -> Void)?
class DraggableView: NSView, NSDraggingSource {
var fileURL: URL!
var dragOperation: NSDragOperation!
var thumbnail: NSImage?
var dragOperationCompleted: (() -> Void)?
private var previewImage: NSImage? {
thumbnail ?? NSImage(contentsOf: fileURL)
}
override func mouseDown(with event: NSEvent) {
let pasteboardItem = NSPasteboardItem()
pasteboardItem.setString(fileURL.absoluteString, forType: .fileURL)
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(self.bounds, contents: previewImage)
beginDraggingSession(with: [draggingItem], event: event, source: self)
}
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return dragOperation
}
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
dragOperationCompleted?()
}
}
func body(content: Content) -> some View {
content
.overlay {
RepresentedView(
fileURL: fileURL,
dragOperation: dragOperation,
thumbnail: thumbnail,
dragOperationCompleted: dragOperationCompleted
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct RepresentedView: NSViewRepresentable {
let fileURL: URL
var dragOperation: NSDragOperation
var thumbnail: NSImage?
var dragOperationCompleted: (() -> Void)?
func makeNSView(context: Context) -> DraggableView {
let view = DraggableView()
view.fileURL = fileURL
view.dragOperation = dragOperation
view.thumbnail = thumbnail
view.dragOperationCompleted = dragOperationCompleted
return view
}
func updateNSView(_ nsView: DraggableView, context: Context) { }
}
}
extension View {
/// Makes the view draggable with the file at the provided URL.
///
/// - Parameters:
/// - fileURL: The file URL of the image that you want to be draggable from the view.
/// - dragOperation: The drag operation to perform when dragging. Defaults to `.copy`.
/// - thumbnail: An optional thumbnail to display when dragging, otherwise the image at fileURL will be shown.
/// - dragOperationCompleted: An optional closure that gets called when the drag operation is completed.
///
/// ```
/// // Usage example:
/// Image("background")
/// .resizable()
/// .draggableImage(fileURL: URL(string: "path_to_your_file")!)
/// ```
func draggableImage(
fileURL: URL,
dragOperation: NSDragOperation = .copy,
thumbnail: NSImage? = nil,
dragOperationCompleted: (() -> Void)? = nil
) -> some View {
self.modifier(
ImageDraggableModifier(
fileURL: fileURL,
thumbnail: thumbnail,
dragOperation: dragOperation,
dragOperationCompleted: dragOperationCompleted
)
)
}
}
@jb-apps
Copy link
Author

jb-apps commented Aug 29, 2023

The default onDrag view modifier in SwiftUI only supports a move drag operation.

This ImageDraggableModifier allows custom drag operations on macOS like .copy using pure SwiftUI.

Usage:

struct ContentView: View {
    let thumbnail: NSImage?
    let imageFileURL: URL
    let image: NSImage
    
    var body: some View {
        Image(nsImage: image)
            .draggableImage(fileURL: url, dragOperation: .copy, thumbnail: thumbnail) {
                print("Drag operation did finish")
            }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment