我有一个自定义集合视图:
import AppKit
final class InternalCollectionView: NSCollectionView {
typealias KeyDownHandler = (_ event: NSEvent) -> Bool
var keyDownHandler: KeyDownHandler? = nil
// Do nothing on Cmd+A
override func selectAll(_ sender: Any?) { }
}
我还有 SwiftUI 的 collectionView,里面使用了一些控制器:
struct FBCollectionView<Content: View>: NSViewControllerRepresentable {
//here some implementation
}
public class NSCollectionController<Content: View>: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
//here some implementation
}
我需要实现逻辑:
首先,我试图在拖动开始时隐藏应用程序。为此,我已经实施了
NSCollectionController
的方法:
public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexPaths: Set<IndexPath>) {
hideApp()
preventHidingItemsDuringDrag(collectionView, indexPaths: indexPaths)
}
func hideApp() {
DispatchQueue.main.async {
NSApplication.shared.hide(self)
}
appShown = false
automaticScroller.updStatus(appDisplayed: appShown)
}
但出于某种原因,这仅适用于第一次拖动(!)在每个后续拖动应用程序不会隐藏
我试图在主线程中运行这段代码,但没有得到任何可用的结果
所以问题是:
addLocalMonitorForEvents
(我考虑过addGlobalMonitorForEvents
,但是......正如所示,它需要应用程序具有辅助功能访问权限)
但是,正如 OP 在评论中所指出的:
仅在释放鼠标按钮后才隐藏应用程序。出于某种原因,collectionView 保存了窗口的绘图(在我的例子中是 NSPanel)。只有在我放下鼠标按钮后才会调用 hideApp()(我在日志中看到了这个)
因此,让我们尝试另一个来监视拖动会话状态。
阅读《Supporting Table View Drag and Drop Through File Promises》,我看到:
当拖动开始时,您采用
协议将数据写入NSPasteboardWriting
。发生拖动时,您确定有效的放置目标。当拖动结束时,您从NSPasteboard
读取拖动数据。”NSPasteboard
接受这一点:
import AppKit
import SwiftUI
public class NSCollectionController<Content: View>: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
// Flag to check whether the app is currently visible.
static var appShown = true
// A helper object for automatically scrolling the collection view.
var automaticScroller: AutomaticScroller!
// NSCollectionViewDelegate
// This function is called when the user starts dragging an item.
// We return our custom pasteboard writer, which also conforms to NSDraggingSource, for the dragged item.
public func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
return MyPasteboardWriter()
}
// This function is called when a dragging session ends. At this point, we reset our appShown flag to true.
public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) {
NSCollectionController.appShown = true
}
// A helper function to hide the app.
static func hideApp() {
DispatchQueue.main.async {
NSApplication.shared.hide(nil)
}
appShown = false
// Here you would call a function to update the automatic scroller.
// automaticScroller.updStatus(appDisplayed: appShown)
}
// Our custom pasteboard writer. This class also implements NSDraggingSource to handle the dragging of the item.
private class MyPasteboardWriter: NSObject, NSPasteboardWriting, NSDraggingSource {
// NSPasteboardWriting
// This function returns the types of data that this object can write to the pasteboard.
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
// You need to implement this method based on the data your items can represent.
// For example, if your items can be represented as strings, you can return [.string].
}
// This function returns a property list that represents the data of this object for a specific type.
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
// You need to implement this method based on the data of your item for the given type.
// For example, if your items can be represented as strings and type is .string, you can return the string representation of your item.
}
// NSDraggingSource
// This function returns the allowed operations (like .copy, .move) when the dragging is outside the source application.
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return [.copy, .move]
}
// This function is called when the dragging image is moved.
// Here we check if the mouse is outside the app window, and if so, we hide the app.
func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
guard let window = NSApplication.shared.mainWindow, NSCollectionController.appShown else { return }
let windowRectInScreenCoordinates = window.convertToScreen(window.frame)
if !windowRectInScreenCoordinates.contains(screenPoint) {
NSCollectionController.hideApp()
}
}
// This function is called when the drag operation ends. There is no need to do anything here in this case.
func draggingSession(_ session: NSDraggingSession, endedAt
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
// You can add any cleanup operations here after a drag operation ends
}
}
}
NSCollectionController
类是 NSCollectionView
的控制器。它处理许多任务,包括充当集合视图的委托和数据源,以及管理拖放交互。
为了在拖动项目移出应用程序窗口时隐藏整个应用程序,想法是自定义类(
MyPasteboardWriter
),它同时符合NSPasteboardWriting
和NSDraggingSource
协议。NSPasteboardWriting
协议使该类能够向粘贴板提供数据(在拖放操作期间使用),而 NSDraggingSource
允许它对拖放事件做出反应。
在
NSDraggingSource
协议中,实现了draggingSession(_:movedTo:)
方法来检查拖动项的位置。如果项目移到应用程序窗口之外,应用程序将被隐藏。这是通过使用 NSApplication.shared.hide(nil)
函数完成的。
appShown
静态变量用于跟踪应用程序当前是否可见。重要的是要防止应用程序连续多次尝试隐藏。
还实现了
draggingSession(_:sourceOperationMaskFor:)
方法,指定在源应用程序外拖动时允许的操作(.copy,.move)
最后,
collectionView(_:draggingSession:endedAt:dragOperation:)
委托方法用于在拖动会话结束时将 appShown
标志重置为真,表示现在可以再次显示应用程序。
功能从未被调用,所以应用程序无法隐藏。movedTo
确保您正确设置了拖动会话,并且您正在拖动的项目使用您的自定义
MyPasteboardWriter
作为其粘贴板编写器。
采用
NSDraggingSource
协议并实现draggingSession(_:movedTo:)
方法的类必须是发起拖动会话时用作源对象的类我不认为在拖动过程中隐藏应用程序会起作用。隐藏窗口是可能的。
子类
NSCollectionView
并覆盖func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint)
。当screenPoint
在窗外时隐藏窗
extension InternalCollectionView {
override func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
super.draggingSession(session, movedTo: screenPoint)
guard let currentWnd = self.window, currentWnd.isVisible else { return }
//if drag is going outside all of FileBo windows
guard NSApp.windows.compactMap({ $0.frame.contains(screenPoint) }).allSatisfy({ $0 == false}) else { return }
currentWnd.setIsVisible(false)
hideApp()
}
}