如何写入NSPasteboard并使用相同的指针获取它

时间:2017-07-26 14:47:15

标签: swift cocoa nsoutlineview nscoding nspasteboard

我有一个符合NSCoding协议的自定义类。下面是我如何实现协议的方法。

User.swift

required init?(coder aDecoder: NSCoder) {
    self.name = aDecoder.decodeObject(forKey: #keyPath(name)) as! String
    self.age = aDecoder.decodeInteger(forKey: #keyPath(age))
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(name, forKey: #keyPath(name))
    aCoder.encode(age, forKey: #keyPath(age))
}

我将阅读选项设置为:

static func readingOptions(forType type: String, pasteboard: NSPasteboard) -> NSPasteboardReadingOptions {
    return .asKeyedArchive
}

只要用户在NSDragPboard上拖动单元格行,此对象就会放在NSOutlineView内。您可以在下面看到实施:

UserViewController.h

var users: User // Store the users to be used in the outlineView

.
.
.

func outlineView(_ outlineView: NSOutlineView, writeItems items: [Any], to pasteboard: NSPasteboard) -> Bool {
    if let user = items.first as? User {
        pasteboard.clearContents()
        pasteboard.writeObjects([user])
        return true
    }
    return false
}

用户通过释放鼠标按钮完成拖动后,应用程序将通过执行以下操作获取粘贴板内容:

func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {

    let pasteboard = info.draggingPasteboard()

    if let userInPasteboard = pasteboard.readObjects(forClasses: [User.self], options: nil)?.first as? User {
        // We can access the user in pasteboard here
    }
}

但问题是,来自粘贴板的数据与outlineView中的数据具有不同的内存地址。

如果我们尝试使用pasteBoard中的用户在outlineView中找到用户,我们找不到它。

for user in self.users {
    if user == userInPasteboard {
        print("We found the user that was placed on pasteboard") // Never executed
    }
}

我可以为User类实现Equatable协议。但我想如果我们能以某种方式使得从粘贴板读取的Object具有与写入粘贴板的Object相同的指针地址,那么它将起作用。

以下是我想要实现的目标:

NSPasteboard writing and reading illustration

有可能实现吗?怎么样?

1 个答案:

答案 0 :(得分:1)

有很多方法可以解决这个问题。既然你(在评论中)说这只是用于在单个大纲视图中重新排序,我将解释我是如何做到的。

首先,我在我的数据源中添加了一个私有变量来跟踪被拖动的项目:

private var itemsBeingDragged = [MyItem]()

由于我会在丢弃时使用它来获取拖动的项目,因此粘贴板上的内容并不重要。我像这样实施了outlineView(_:pasteboardWriterForItem:)

func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
    guard let node = item as? MyNode else { return nil }
    let pasteboardItem = NSPasteboardItem()
    pasteboardItem.setString(String(describing: node), forType: dragType)
    return pasteboardItem
}

要设置和清除itemsBeingDragged,我实施了以下方法:

func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
    itemsBeingDragged = draggedItems.flatMap({ $0 as? MyItem })
}

func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
    itemsBeingDragged = []
}

最后,我像这样实施了outlineView(_:acceptDrop:item:childIndex:)

func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
    guard
        (info.draggingSource() as? NSOutlineView) === outlineView,
        let dropTarget = item as? MyItem
        else { return false }

    // Update model using `dropTarget`, `index`, and `itemsBeingDragged`.
    // Update `outlineView` to match.
    return true
}

如果拖动来自其他应用,则info.draggingSource()nil。如果拖动来自同一个应用程序,则它是提供拖动项目的对象。所以我要做的第一件事就是确保拖拽来自拖放所在的相同轮廓视图。

更新

如果拖动源位于另一个应用程序中,则获取指向原始拖动项目的指针毫无意义,因为指针在进程边界内无效 - 原始拖动项目在您的进程中不存在。跨越流程边界拖动项目的唯一有意义的方法是实际提供拖动项目的序列化表示。

如果拖动源也在您的应用程序中,那么您可以在粘贴板上粘贴指针,但这样做仍然不是内存安全的。最好做一些事情,比如定义一个从源获取拖动项目的协议:

protocol MyDragSource {
    var itemsBeingDragged: [Any]?
}

然后,在acceptDrop

guard
    let sameAppSource = info.draggingSource(),
    let source = (sameAppSource as? MyDragSource) ?? (sameAppSource as? NSTableView)?.dataSource as? MyDragSource,
    let draggedItems = source.itemsBeingDragged?.flatMap({ $0 as? AcceptableItem }),
    draggedItems.count > 0
    else { return false }

...其中AcceptableItem是您用于大纲查看项目的任何类型。

使用info.draggingSource()并不可耻。这是有原因的。