I'm working on a Cocoa application to drag and drop files between two NSTableViews. Rather than using just the URL, I want to use a custom struct so I can have access to more data if needed and not make constant calls to the FileManager.
I believe that I need to implement conform my custom Pasteboard Utility to NSPasteboardReading so I can properly digest the data on the receiving table.
I'm unsure of exactly what's needed to set the init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType)
function as required when dealing with the custom struct that I'm using in the Pasteboard.
Frankly, I'm unsure of how to use the property list in this situation as I've generally only used it in setting global application plists in the past.
Sadly, there's not a whole lot of resources here. Most examples I've seen generally reference JSON objects for the Property list. I'm unsure if I need to extract the data from the custom Type into an array of Data or String types.
Any guidance here on the implementation or even better guidance on what's possible with Property Lists would be most appreciated!
Custom Struct passed to the PasteBoard:
struct TidiFile {
var url : URL?
var createdDateAttribute : Date?
var modifiedDateAttribute : Date?
var fileSizeAttribute: Int?
//setting for a nil init so this can return nil values in case of failure to set attributes
init( url : URL? = nil,
createdDateAttribute : Date? = nil,
modifiedDateAttribute : Date? = nil,
fileSizeAttribute: Int? = nil) {
self.url = url
self.createdDateAttribute = createdDateAttribute
self.modifiedDateAttribute = modifiedDateAttribute
self.fileSizeAttribute = fileSizeAttribute
}
}
Table View Controller: Where I write the item to the Pasteboard
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
return PasteboardWriter(tidiFile: tableSourceTidiFileArray[row], at: row)
}
Table View Controller: Where I want to accept the drop and move the file
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
let pasteboard = info.draggingPasteboard
let pasteboardItems = pasteboard.pasteboardItems
}
Custom Pasteboard Utility:
import Foundation
import Cocoa
class PasteboardWriter: NSObject, NSPasteboardWriting, NSPasteboardReading {
required init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) {
// Need to implement
}
var tidiFile : TidiFile
var index: Int
init(tidiFile : TidiFile, at index: Int) {
self.tidiFile = tidiFile
self.index = index
}
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
return [.tableViewIndex, .tidiFile]
}
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
switch type {
case .tidiFile:
return tidiFile
case .tableViewIndex:
return index
default:
return nil
}
}
static func readableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
return [.tableViewIndex, .tidiFile]
}
}
extension NSPasteboard.PasteboardType {
static let tableViewIndex = NSPasteboard.PasteboardType("com.bradzellman.tableViewIndex")
static let tidiFile = NSPasteboard.PasteboardType("com.bradzellman.tidiFile")
}
extension NSPasteboardItem {
open func integer(forType type: NSPasteboard.PasteboardType) -> Int? {
guard let data = data(forType: type) else { return nil }
let plist = try? PropertyListSerialization.propertyList(
from: data,
options: .mutableContainers,
format: nil)
return plist as? Int
}
}
First of all to be able to drag&drop a custom object this object must be a subclass of NSObject
.
This is a quick&dirty implementation with non-optional types. The data is serialized to and from Property List with Codable
. The protocol methods init(from decoder
and encode(to encoder
are synthesized.
In init?(pasteboardPropertyList
you have to decode an instance and create a new one with the standard initializer.
final class TidiFile : NSObject, Codable {
var url : URL
var createdDateAttribute : Date
var modifiedDateAttribute : Date
var fileSizeAttribute: Int
init(url: URL, createdDateAttribute: Date, modifiedDateAttribute: Date, fileSizeAttribute: Int) {
self.url = url
self.createdDateAttribute = createdDateAttribute
self.modifiedDateAttribute = modifiedDateAttribute
self.fileSizeAttribute = fileSizeAttribute
}
convenience init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) {
guard let data = propertyList as? Data,
let tidi = try? PropertyListDecoder().decode(TidiFile.self, from: data) else { return nil }
self.init(url: tidi.url, createdDateAttribute: tidi.createdDateAttribute, modifiedDateAttribute: tidi.modifiedDateAttribute, fileSizeAttribute: tidi.fileSizeAttribute)
}
}
extension TidiFile : NSPasteboardWriting, NSPasteboardReading
{
public func writingOptions(forType type: NSPasteboard.PasteboardType, pasteboard: NSPasteboard) -> NSPasteboard.WritingOptions {
return .promised
}
public func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
return [.tidiFile]
}
public func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
if type == .tidiFile {
return try? PropertyListEncoder().encode(self)
}
return nil
}
public static func readableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
return [.tidiFile]
}
public static func readingOptions(forType type: NSPasteboard.PasteboardType, pasteboard: NSPasteboard) -> NSPasteboard.ReadingOptions {
return .asData
}
}