如何在 macOS/AppKit 中的窗口选项卡上接受拖动项目的放置?

Fra*_*k R 16 macos drag-and-drop appkit swift

鼠标光标将书签文件拖动到 Safari 窗口选项卡

\n

在 Safari 中,我可以将项目(例如 URL,甚至是取景器中的 .webloc 书签)直接拖到选项卡上以打开。

\n

如何使窗口选项卡栏项目在我自己的 AppKit 应用程序中查看放置目标?

\n

I\xe2\x80\x99d 希望能够接受删除NSPasteboard类似于NSView实例的方式:

\n
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {\n    // Handle drop\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但选项卡栏和包含的NSTabButton实例是由系统提供的。\n子类化或扩展NSTabButton\xe2\x80\x99 似乎不起作用,因为\xe2\x80\x99 是私有的。

\n

Fra*_*k R 3

Brian Webster\xe2\x80\x99 的精彩回答(谢谢!)启发了这个解决方案。

\n

带有可以接受拖放的选项卡的窗口

\n

我将演示代码添加到 GitHub 上的一个完全运行的项目中

\n

首先,我们在创建窗口时将自定义附件视图添加到window\xe2\x80\x99s中。tab我们传递一个引用到NSWindowController以便每当选项卡项上有某些内容时我们可以轻松通知它。

\n
window.tab.accessoryView = TabAccessoryView(windowController: windowController)\n
Run Code Online (Sandbox Code Playgroud)\n

此自定义accessoryView( TabAccessoryView) 不是接受drop 的视图,因为附属视图仅限于NSStackView,仅覆盖标题标签旁边的选项卡的一部分。

\n

因此,我们使用\xe2\x80\x99s 视图层次结构的accessoryView一部分这一事实来NSTabButtonTabDropTargetViewNSStackView xe2\x80\xa6

\n
class TabAccessoryView: NSView {\n\n    weak private(set) var windowController: NSWindowController?\n\n    private let tabDropTargetView: TabDropTargetView\n\n    init(windowController: NSWindowController? = nil) {\n        self.windowController = windowController\n        self.tabDropTargetView = TabDropTargetView(windowController: windowController)\n        super.init(frame: .zero)\n    }\n\n    required init?(coder: NSCoder) {\n        fatalError("init(coder:) has not been implemented")\n    }\n\n    override func viewDidMoveToWindow() {\n        guard tabDropTargetView.superview == nil else { return }\n\n        // DEBUG: Highlight accessory view\n        wantsLayer = true\n        layer?.backgroundColor = NSColor.red.withAlphaComponent(0.1).cgColor\n\n        // The NSTabButton close button, title, and accessory view are contained in a stack view:\n        guard let stackView = superview as? NSStackView,\n              let backgroundView = stackView.superview else { return }\n\n        // Add the drop target view behind the NSTabButton\xe2\x80\x99s NSStackView and pin it to the edges\n        backgroundView.addSubview(tabDropTargetView, positioned: .below, relativeTo: stackView)\n        tabDropTargetView.translatesAutoresizingMaskIntoConstraints = false\n        tabDropTargetView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor).isActive = true\n        tabDropTargetView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor).isActive = true\n        tabDropTargetView.topAnchor.constraint(equalTo: backgroundView.topAnchor).isActive = true\n        tabDropTargetView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor).isActive = true\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\xa6 将处理掉落的物品:

\n
class TabDropTargetView: NSView {\n    private(set) weak var windowController: NSWindowController?\n\n    let allowedDropTypes: Array<NSPasteboard.PasteboardType> = [.URL, .fileContents, .string, .html, .rtf]\n\n    init(windowController: NSWindowController? = nil) {\n        self.windowController = windowController\n        super.init(frame: .zero)\n\n        // Tell the system that we accept drops on this view\n        registerForDraggedTypes(allowedDropTypes)\n    }\n\n    required init?(coder: NSCoder) {\n        fatalError("init(coder:) has not been implemented")\n    }\n\n    override func viewDidMoveToWindow() {\n        // DEBUG: Highlight drop target view\n        wantsLayer = true\n        layer?.backgroundColor = NSColor.green.withAlphaComponent(0.05).cgColor\n    }\n\n    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {\n        return .copy\n    }\n\n    override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {\n        return .copy\n    }\n\n    override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {\n        // Optional: Ignore drags from the same window\n        guard (sender.draggingSource as? NSView)?.window != window else { return false }\n\n        // Check if the dropped item contains text:\n        let pasteboard = sender.draggingPasteboard\n        guard let availableType = pasteboard.availableType(from: allowedDropTypes),\n              let text = pasteboard.string(forType: availableType) else {\n            return false\n        }\n\n        if let windowController = windowController as? WindowController {\n            // Use the reference to the tab\xe2\x80\x99s NSWindowController to pass the dropped item\n            windowController.handleDroppedText(text)\n        }\n\n        return true\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n