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

标签 swift cocoa nsoutlineview nscoding nspasteboard

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

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
}

每当用户在 NSOutlineView 上拖动单元格行时,此对象将被放入 NSDragPboard 中。你可以看到下面的实现:

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 协议(protocol)。但我认为,如果我们能以某种方式使从粘贴板读取的对象与写入粘贴板的对象具有相同的指针地址,它就可以工作。

下面是我要实现的目标:

NSPasteboard writing and reading illustration

有可能实现吗?怎么办?

最佳答案

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

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

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。如果拖动来自同一个应用程序,则它是提供拖动项的对象。因此,我做的第一件事是确保拖动来自与拖放所在的同一轮廓 View 。

更新

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

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

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 是您用于大纲 View 项目的任何类型。

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

关于swift - 如何写入 NSPasteboard 并使用相同的指针获取它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45330382/

相关文章:

ios - Swift - tableview.reloadData() 使应用程序崩溃

swift - 如何在 viewDidLoad 中调用 UIButton 的扩展

objective-c - 如何获取CABasicAnimation进度?

cocoa - 如何在 NSOutlineView 中禁用取消选择行

objective-c - 过滤 NSOutlineView/NSTreeController

objective-c - 如何获取 NSOutLineView 中的特定项目?

ios - 在没有方案的情况下验证 URL

ios - 为 iOS8 和 iOS7 兼容性转换 iOS9 合并触摸绘图代码

objective-c - 如何将 Cocoa 坐标从左上角 == 原点转换为左下角 == 原点

objective-c - 调用父类(super class)的 init 方法后强制初始化父类(super class)的 ivar