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

oknwwptz  于 2023-03-11  发布在  Swift
关注(0)|答案(2)|浏览(132)

在Safari中,我可以将一个项目(如URL,甚至是finder中的.webloc书签)直接拖到标签页上打开。

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

我希望能够接受删除NSPasteboard,类似于NSView示例:

override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
    // Handle drop
}

但是标签栏和包含的NSTabButton示例是系统提供的,子类化或扩展NSTabButton似乎不起作用,因为它是私有的。

soat7uwm

soat7uwm1#

布莱恩·韦伯斯特的精彩回答(谢谢!)启发了这个解决方案。

我添加了demo code to a fully working project on GitHub
首先,我们在创建窗口时向windowtab添加一个自定义附件视图,并传递一个对NSWindowController的引用,以便在选项卡项上放置内容时可以轻松通知它。

window.tab.accessoryView = TabAccessoryView(windowController: windowController)

这个定制的accessoryViewTabAccessoryView)不是接受拖放的视图,因为附件视图被限制为NSStackView,连同关闭按钮和标题标签,只覆盖标题标签旁边的一部分选项卡。
因此,我们利用accessoryViewNSTabButton的视图层次结构的一部分这一事实,在NSStackView * 后面 * 注入另一个自定义视图(TabDropTargetView)...

class TabAccessoryView: NSView {

    weak private(set) var windowController: NSWindowController?

    private let tabDropTargetView: TabDropTargetView

    init(windowController: NSWindowController? = nil) {
        self.windowController = windowController
        self.tabDropTargetView = TabDropTargetView(windowController: windowController)
        super.init(frame: .zero)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidMoveToWindow() {
        guard tabDropTargetView.superview == nil else { return }

        // DEBUG: Highlight accessory view
        wantsLayer = true
        layer?.backgroundColor = NSColor.red.withAlphaComponent(0.1).cgColor

        // The NSTabButton close button, title, and accessory view are contained in a stack view:
        guard let stackView = superview as? NSStackView,
              let backgroundView = stackView.superview else { return }

        // Add the drop target view behind the NSTabButton’s NSStackView and pin it to the edges
        backgroundView.addSubview(tabDropTargetView, positioned: .below, relativeTo: stackView)
        tabDropTargetView.translatesAutoresizingMaskIntoConstraints = false
        tabDropTargetView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor).isActive = true
        tabDropTargetView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor).isActive = true
        tabDropTargetView.topAnchor.constraint(equalTo: backgroundView.topAnchor).isActive = true
        tabDropTargetView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor).isActive = true
    }

}

...将处理放下的项目:

class TabDropTargetView: NSView {
    private(set) weak var windowController: NSWindowController?

    let allowedDropTypes: Array<NSPasteboard.PasteboardType> = [.URL, .fileContents, .string, .html, .rtf]

    init(windowController: NSWindowController? = nil) {
        self.windowController = windowController
        super.init(frame: .zero)

        // Tell the system that we accept drops on this view
        registerForDraggedTypes(allowedDropTypes)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidMoveToWindow() {
        // DEBUG: Highlight drop target view
        wantsLayer = true
        layer?.backgroundColor = NSColor.green.withAlphaComponent(0.05).cgColor
    }

    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        return .copy
    }

    override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
        return .copy
    }

    override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        // Optional: Ignore drags from the same window
        guard (sender.draggingSource as? NSView)?.window != window else { return false }

        // Check if the dropped item contains text:
        let pasteboard = sender.draggingPasteboard
        guard let availableType = pasteboard.availableType(from: allowedDropTypes),
              let text = pasteboard.string(forType: availableType) else {
            return false
        }

        if let windowController = windowController as? WindowController {
            // Use the reference to the tab’s NSWindowController to pass the dropped item
            windowController.handleDroppedText(text)
        }

        return true
    }
}
zengzsys

zengzsys2#

我没有试过这个,所以我不知道它是否会工作,但它值得一试。
其思想是使用与窗口关联的NSWindowTab对象(参见NSWindow.tab),并在选项卡上设置一个accessoryView,该选项卡可以注册为接受拖动。您可以在附件视图上设置约束,以尝试使其填充整个选项卡-您可能需要覆盖用于附件视图的NSView子类中的updateConstraints,因为一旦AppKit实际将其插入到的视图层次结构中,就可以将其固定到其超级视图标签本身。
我不知道的是AppKit是否会一直强制你的附件视图只填充标签页中为你绘制的标题尚未占用的空间。那么您可能还需要为NSWindowTab示例设置一个空标题,然后在自定义视图中使用您自己的文本字段来显示该标题。不要强迫你的视图占据整个选项卡,在这种情况下,用户只能放在你的附件视图占据的任何空间上。
如果你有机会尝试,我会很好奇,看看它是否真的工作!

相关问题