Swift 如何写入NSPasteboard并使用相同的指针获取它
我有一个符合NSCoding协议的自定义类。下面是我如何实现协议的方法 User.swiftSwift 如何写入NSPasteboard并使用相同的指针获取它,swift,cocoa,nsoutlineview,nscoding,nspasteboard,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:
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
}
}
但问题是,粘贴板中的数据与大纲视图中的数据具有不同的内存地址
如果我们试图使用粘贴板上的用户在大纲视图中查找用户,则无法找到它
for user in self.users {
if user == userInPasteboard {
print("We found the user that was placed on pasteboard") // Never executed
}
}
我可以为用户类实现equalable
协议。但我认为,如果我们能以某种方式使从粘贴板读取的对象具有与写入粘贴板的对象相同的指针地址,它就会工作
以下是我努力实现的目标:
有可能实现吗?如何处理?有很多方法可以解决这个问题。既然您(在评论中)说这只是为了在单个大纲视图中重新排序,我将解释我是如何做到这一点的 首先,我在数据源中添加了一个私有变量来跟踪正在拖动的项目:
private var itemsBeingDragged = [MyItem]()
因为我将在放置时使用它来获取拖动的项目,所以粘贴板上的内容实际上并不重要。我实现了outlineView(uu: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
}
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
}
为了设置和清除项beingdrable
,我实现了以下方法:
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(uu:acceptDrop:item:childIndex:)
如下:
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
}
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()
没有什么丢脸的。这是有原因的。不,你不能这样做。拖放是否在同一大纲视图中?@Willeke是的,它在同一大纲视图中。只是为了重新排序。将索引路径放在粘贴板上。请注意,如果将索引路径放在粘贴板上,并且应用程序中有多个支持拖动的大纲视图,那么(接受拖放时)您仍然需要查看拖动的来源,并且需要将索引路径映射回对象。这不一定比直接到拖动对象的拖动源更简单或更好,而且效率肯定更低。@EdwardAnthony为什么要执行user==userInPasteboard
?你想在同一个大纲视图中重新排序吗?经过这么多的尝试,包括info.enumerateDraggingItems
我发现没有比这更好的方法了。谢谢。不要使用变量,使用粘贴板。