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

vktxenjb  于 2023-05-16  发布在  Swift
关注(0)|答案(1)|浏览(125)

bounty还有5天到期。回答此问题可获得+300声望奖励。Andrew_STOP_RU_WAR_IN_UA希望引起更多关注这个问题。

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

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

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

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

q3qa4bjr1#

您可以考虑使用addLocalMonitorForEvents(我考虑过addGlobalMonitorForEvents,但是...如此处所示,应用程序需要具有可访问性访问权限)
然而,正如执行部分在评论中指出的:
只有在释放鼠标按钮后才隐藏应用程序。出于某种原因,collectionView保存了窗口的绘制(在我的例子中是NSPanel)。hideApp()只有在我放下鼠标按钮之后才被调用(我在日志中看到了这一点)
因此,让我们尝试另一个来监视拖动会话状态。
首先,更新您的NSCollectionController以符合NSDraggingSource。该协议有一个方法draggingSession(_:movedTo:),每当拖动项移动时都会调用该方法。它提供了拖动项的屏幕点,您可以使用该点来检测该项是否已被拖动到应用程序窗口之外。
下面是一个例子:

public class NSCollectionController<Content: View>: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource, QLPreviewPanelDataSource, QLPreviewPanelDelegate, NSDraggingSource {
    // ... Existing code ...

    public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexPaths: Set<IndexPath>) {
        session.draggingSource = self
        // ... Your existing code ...
    }

    public func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
        guard let window = self.view.window else { return }
        
        let windowRectInScreenCoordinates = window.convertToScreen(window.frame)
        
        // Check if the mouse has moved outside the window
        if !windowRectInScreenCoordinates.contains(screenPoint) {
            self.hideApp()
        }
    }

    // ... Remaining methods ...
}

在本例中,我们将selfNSCollectionController)指定为collectionView(_:draggingSession:willBeginAt:forItemsAt:)方法中的拖动源。这允许每当拖动项被移动时调用draggingSession(_:movedTo:)方法。
然后,在draggingSession(_:movedTo:)方法中,我们获取屏幕坐标中的窗口框架,并检查新的鼠标位置(screenPoint参数)是否在该框架之外。如果是,则调用hideApp()方法。
这种方法应该在拖动项移出窗口后立即隐藏应用程序,并且不需要更改任何系统安全设置。
至于第一次拖动后每次鼠标移动时应用程序都会隐藏的问题,这可能是因为即使您没有拖动任何东西,也会调用hideApp()方法。若要避免这种情况,请确保仅在拖动会话处于活动状态时隐藏应用。您可以通过在调用hideApp()之前检查isDragging标志来实现这一点。
例如:

public func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
    guard let window = self.view.window, isDragging else { return }
    
    let windowRectInScreenCoordinates = window.convertToScreen(window.frame)
    
    // Check if the mouse has moved outside the window
    if !windowRectInScreenCoordinates.contains(screenPoint) {
        self.hideApp()
    }
}

请记住,在拖动会话开始时将isDragging设置为true,在拖动会话结束时将false设置为false
完整代码:

public class NSCollectionController<Content: View>: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource, QLPreviewPanelDataSource, QLPreviewPanelDelegate, NSDraggingSource {

    // Other properties...
    private var isDragging = false

    // Other methods...

    public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexPaths: Set<IndexPath>) {
        isDragging = true
        session.draggingSource = self
        preventHidingItemsDuringDrag(collectionView, indexPaths: indexPaths)
    }

    public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) {
        isDragging = false
    }

    public func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
        guard let window = self.view.window, isDragging else { return }
        let windowRectInScreenCoordinates = window.convertToScreen(window.frame)
        if !windowRectInScreenCoordinates.contains(screenPoint) {
            hideApp()
        }
    }

    // Other methods...

    func hideApp() {
        DispatchQueue.main.async {
            NSApplication.shared.hide(self)
        }
        appShown = false
        automaticScroller.updStatus(appDisplayed: appShown)
    }
}

collectionView(_:draggingSession:willBeginAt:forItemsAt:)方法中,我们将isDragging设置为true,并将self设置为拖动源。
draggingSession(_:movedTo:)方法中,我们检查拖动的项是否已移动到窗口之外。如果有,我们调用hideApp()
最后,在collectionView(_:draggingSession:endedAt:dragOperation:)方法中,我们将isDragging设置为false以指示拖动会话已经结束。
这应确保当您将项目拖动到应用程序窗口外时,应用程序处于隐藏状态,并且在拖动操作结束后简单移动鼠标时,应用程序不会隐藏。

相关问题