I solved this.
Prerequisites:
NSTableView instance must have its DataSource object assigned:
myNSTableViewInstance.dataSource = XXX
The DataSource object conforms to NSTableViewDataSource protocol, having two necessary functions implemented (will discuss below).
Whenever appropriate (e.g.: .windowDidLoad(), .viewDidLoad(), etc.), register the .registerForDraggedTypes for NSTableView instance.
In the prerequisite 3, the registration method is:
// Constructing the NSPasteboard.PasteboardType.
tblClients.registerForDraggedTypes([.init(rawValue: kUTTypeFileURL as String)]) // Compatible with macOS 10.9 Mavericks.
tblClients.registerForDraggedTypes([.fileURL]) // Since macOS 10.13 High Sierra.
In the prerequisite 2, the registration method is:
// Constructing the NSPasteboard.PasteboardType.
.init(rawValue: kUTTypeApplicationBundle as String)
We start implementing. Suppose that (in this case):
the DataSource object is the WindowController itself.
the name of the NSTableView instance is tblClients.
Since the two functions mentioned above shares a lot of things, we extract them as a standalone function with lambda expressions:
/// See whether the incoming NSDraggingInfo is an array of App Bundles.
/// - Parameters:
/// - info: the incoming NSDraggingInfo object。
/// - onError: On error perform this given lambda expression.
/// - handler: When conditions are met, perform this given lambda expression to process the URL array.
private func validatePasteboardForAppBundles(
neta info: NSDraggingInfo, onError: @escaping () -> Void?, handler: @escaping ([URL]) -> Void?
) {
let board = info.draggingPasteboard
let type = NSPasteboard.PasteboardType(rawValue: kUTTypeApplicationBundle as String)
let options: [NSPasteboard.ReadingOptionKey: Any] = [
.urlReadingFileURLsOnly: true,
.urlReadingContentsConformToTypes: [type],
]
guard let urls = board.readObjects(forClasses: [NSURL.self], options: options) as? [URL], !urls.isEmpty else {
onError()
return
}
handler(urls)
}
Finally, the two implementations.
The one to check whether the dragged file object is the one we need to let the NSTableView response to:
func tableView(
_: NSTableView, validateDrop info: NSDraggingInfo, proposedRow _: Int,
proposedDropOperation _: NSTableView.DropOperation
) -> NSDragOperation {
var result = NSDragOperation.copy
validatePasteboardForAppBundles(
neta: info, onError: { result = .init(rawValue: 0) }, // NSDragOperationNone。
handler: { _ in }
)
return result
}
The handling process once the mouse click releases:
func tableView(
_: NSTableView, acceptDrop info: NSDraggingInfo,
row _: Int, dropOperation _: NSTableView.DropOperation
) -> Bool {
var result = true // Outer report: Operation successful or not.
validatePasteboardForAppBundles(
neta: info, onError: { result = false } // NSDragOperationNone。
) { theURLs in
var dealt = false // Inner report. Will be true if at least one URL entry is processed with successful result.
theURLs.forEach { url in
// See whether this is an app bundle with valid bundleID:
guard let bundle = Bundle(url: url), let bundleID = bundle.bundleIdentifier else { return }
// Performing customized operations toward this bundleID string:
self.applyNewValue(bundleID, highMitigation: true)
// Mark this operation as "successful".
dealt = true
}
// Tell the outer report that whether we did at least one successful operation.
result = dealt
}
defer { if result { self.tblClients.reloadData() } }
return result
}
This should work unless there are other things screwed up.
$ EOF.