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

问题描述 投票:0回答:2

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

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

但出于某种原因,这仅适用于第一次拖动(!)在每个后续拖动应用程序不会隐藏

我试图在主线程中运行这段代码,但没有得到任何可用的结果

所以问题是:

  • 如何在应用程序外拖动时隐藏应用程序?
swift macos drag appkit nscollectionview
2个回答
1
投票

您可能会考虑使用

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:)
    方法的类必须是发起拖动会话时用作源对象的类
    如果您使用不同的对象作为源,则不会调用该方法。


1
投票

我不认为在拖动过程中隐藏应用程序会起作用。隐藏窗口是可能的。

子类

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

© www.soinside.com 2019 - 2024. All rights reserved.