swift NSCollectionView -如何在拖动应用程序的窗口外时隐藏应用程序?

sxpgvts3  于 2023-05-21  发布在  Swift
关注(0)|答案(2)|浏览(114)

我有一个自定义集合视图:

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)
}

但由于某种原因,这只适用于第一次拖动(!)在每个以下拖动应用程序不隐藏
我试图在主线程中运行此代码,但没有得到任何可用的结果
所以问题是

  • 如何在应用程序外部拖动时隐藏应用程序?
s4n0splo

s4n0splo1#

您可以考虑使用addLocalMonitorForEvents(我考虑过addGlobalMonitorForEvents,但是...如此处所示,应用程序需要具有可访问性访问权限)
然而,正如执行部分在评论中指出的:
只有在释放鼠标按钮后才隐藏应用程序。出于某种原因,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的控制器。它处理许多任务,包括充当集合视图的委托和数据源,以及管理拖放交互。
为了在拖动项目移出应用程序窗口时隐藏整个应用程序,我们的想法是使用一个符合NSPasteboardWritingNSDraggingSource协议的自定义类(MyPasteboardWriter)。
NSPasteboardWriting协议允许类向粘贴板(在拖放操作期间使用)提供数据,而NSDraggingSource允许它对拖放事件做出React。
NSDraggingSource协议中,实现draggingSession(_:movedTo:)方法来检查拖动项的位置。如果该项移动到应用程序窗口之外,则应用程序将被隐藏。这是通过使用NSApplication.shared.hide(nil)函数来完成的。
appShown静态变量用于跟踪应用程序当前是否可见。防止应用程序连续多次尝试隐藏非常重要。
draggingSession(_:sourceOperationMaskFor:)方法还用于指定在源应用程序外部拖动时允许的操作(.copy,.move)。
最后,collectionView(_:draggingSession:endedAt:dragOperation:) delegate方法用于在拖动会话结束时将appShown标志重置回true,表示现在可以再次显示应用程序。
movedTo函数从未调用,因此无法隐藏应用程序。

  • 确保正确设置了拖动会话,并且正在拖动的项目使用自定义MyPasteboardWriter作为其粘贴板编写器。
  • 采用NSDraggingSource协议并实现draggingSession(_:movedTo:)方法的类必须是在启动拖动会话时用作源对象的类。

如果使用其他对象作为源,则不会调用该方法。

frebpwbc

frebpwbc2#

我不认为在拖动过程中隐藏应用程序会起作用。可以隐藏窗口。
子类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()
    }
}

相关问题