布莱恩·韦伯斯特的精彩回答(谢谢!)启发了这个解决方案.
我加了demo code to a fully working project on GitHub.
首先,我们在创建窗口时向window
的tab
添加一个自定义附件视图.我们传递一个对NSWindowController
的引用,以便无论何时在选项卡项上有东西落下时都可以很容易地通知它.
window.tab.accessoryView = TabAccessoryView(windowController: windowController)
该定制accessoryView
(TabAccessoryView
)是将接受拖放的视图not,因为附件视图与关闭按钮和标题标签一起被限制在NSStackView
中,仅覆盖标题标签旁边的标签的一部分.
因此,取而代之的是,我们利用accessoryView
是NSTabButton
的视图层次 struct 的一部分这一事实来注入另一个定制视图(TabDropTargetView
)behind即NSStackView
…
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
}
}