I have a UITableView that displays a Group/Note hierarchy analogous to a Finder Folder/Files hierarchy. I have implemented Drag and Drop such that if I drag a Note to the Finder Desktop an HTML file is created of the Note, and if I drag a Group to the Finder Desktop a Text file is created of the Group's title. Here is the Helper Dragging extension that I've implemented:
//
// Model+Dragging.swift
//
/*
Abstract:
Helper methods for providing and consuming drag-and-drop data.
*/
import UIKit
import MobileCoreServices
// Conditionalize Drag and Drop so it is not in iOS target.
#if targetEnvironment(macCatalyst)
extension Model {
/**
A helper function that serves as an interface to the data model,
called by the implementation of the `tableView(_ canHandle:)` method.
*/
func canHandle(_ session: UIDropSession) -> Bool {
// In order to enable dragging all text type files changed the class
// loadable objects from NSString to my custom TEXTClass.
return session.canLoadObjects(ofClass: TEXTClass.self)
}
/**
A helper function that serves as an interface to the data model, called
by the `tableView(_:itemsForBeginning:at:)` method.
*/
func dragItems(for indexPath: IndexPath) -> [UIDragItem] {
let itemProvider = NSItemProvider()
let item = self.getDisplayItem(for: indexPath.row)
if item is Note {
let note:Note = item as! Note
let html = note.noteHTML
let data = html?.data(using: .utf8)
// Drag to finder creates an html file.
itemProvider.suggestedName = note.title + ".html"
itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeData as String,
visibility: .all) { completion in
completion(data, nil)
return nil
}
} else {
let group:Group = item as! Group
let title = group.title
let data = title?.data(using: .utf8)
// Drag to finder creates a text file.
itemProvider.suggestedName = group.title + ".text"
itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeData as String,
visibility: .all) { completion in
completion(data, nil)
return nil
}
}
return [
UIDragItem(itemProvider: itemProvider)
]
}
}
#endif
I would now like to change the result of dragging a Group to the Finder. Instead of creating a Text file from the drag data, I would like to create a Folder containing the Group's Notes.
Note: I am unconcerned with the task of assembling the Group/Notes hierarchies as Folder/Files hierarchies because I already have implemented such previously as a menu export command. My concern is where and how I can communicate it to the Finder in the Drag and Drop process.
As a first step I thought I would simply create an empty folder from the drag of a Group. So far, none of my experiments have been successful. Every variation of itemProvider.registerDataRepresentation(forTypeIdentifier:
or itemProvider.registerFileRepresentation(forTypeIdentifier:
or registerItem(forTypeIdentifier:loadHandler:
that I have tried has failed to produce anything but empty TEXT files, if they worked at all.
It is my understanding that itemProviders may provide directories instead of files, but I have been unable to find any examples of such.
My problem may be a lack of understanding of the syntax and usage of the NSItemProvider.LoadHandler
completion block.
Any Swift examples on point would be greatly appreciated!
I was able to craft a solution with the help of this answer to another NSItemProvider stackoverflow.com question.
The solution uses a loadHandler
to build the directory tree in registerDataRepresentation
:
extension Model {
…
…
…
/**
A helper function that serves as an interface to the data model, called
by the `tableView(_:itemsForBeginning:at:)` method.
*/
// Implement Menu command "ExportGroup as HTML…" as a Drag and Drop feature.//leg20240724 - TopXNotes_Catalyst_v3.0.0
func dragItems(for indexPath: IndexPath) -> [UIDragItem] {
let itemProvider = NSItemProvider()
if item is Note {
let data = …
// Drag to Finder creates an html file.
itemProvider.suggestedName = …
itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeData as String, visibility: .all) { completion in
completion(data, nil)
return nil
}
} else {
// Drag to Finder creates a folder tree.
itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeFileURL as String, visibility: .all, loadHandler: loadFileForItemProvider)
}
return [
UIDragItem(itemProvider: itemProvider)
]
}
// Retrieve the Group tree from the tableView and assemble the
// corresponding Directory tree for the Finder.
//
func loadFileForItemProvider(onComplete callback: @escaping (Data?, Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: 100)
// Build the Group directory tree in a temporary location and
// provide the URL in the callback.
let groupURL = …
Task.detached {
do {
// Drop directory tree on the Finder.
callback(groupURL?.dataRepresentation, nil)
progress.completedUnitCount = 100
}
}
return nil
}
}