NSCollectionView拖拽至应用窗口外时如何隐藏应用?
问题描述
我自定义了InternalCollectionView,同时实现了适配SwiftUI的FBCollectionView及NSCollectionController。需求如下:
- 拖拽时保持项在原位置显示(已实现)
- 拖拽至应用窗口外时隐藏应用
目前尝试在NSCollectionController的collectionView(_:draggingSession:willBeginAt:forItemsAt:)方法中调用hideApp(),但仅首次拖拽时应用会隐藏,后续拖拽无效果,即使切换至主线程执行也无效。
相关代码
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?) { } }
struct FBCollectionView<Content: View>: NSViewControllerRepresentable { //here some implementation } public class NSCollectionController<Content: View>: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource, QLPreviewPanelDataSource, QLPreviewPanelDelegate { //here some implementation }
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) }
解决方案
方案1:监听拖拽位置,判断出窗口后再隐藏
放弃在拖拽开始时直接隐藏,改为实时监听拖拽位置,当检测到拖拽点超出应用窗口范围时执行隐藏:
public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexPaths: Set<IndexPath>) { preventHidingItemsDuringDrag(collectionView, indexPaths: indexPaths) // 定时检查拖拽位置 Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self, weak session] timer in guard let self = self, let session = session else { timer.invalidate() return } guard let windowFrame = self.view.window?.frame else { return } let dragPoint = session.draggingLocation // 判断拖拽点是否在窗口外 if !windowFrame.contains(dragPoint) { self.hideApp() timer.invalidate() } } } // 拖拽结束时可做清理工作 public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) { // 清理相关资源(如定时器会自动失效,此方法可选) }
方案2:先激活应用再执行隐藏
首次隐藏后应用可能处于非活跃状态,hide(self)无法正确触发,先激活再隐藏:
func hideApp() { DispatchQueue.main.async { NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.hide(self) } appShown = false automaticScroller.updStatus(appDisplayed: appShown) }
方案3:直接隐藏主窗口替代应用级隐藏
如果应用级隐藏存在状态问题,直接操作主窗口:
func hideApp() { DispatchQueue.main.async { NSApplication.shared.mainWindow?.orderOut(self) } appShown = false automaticScroller.updStatus(appDisplayed: appShown) }
方案4:同步appShown与应用实际状态
确保appShown标记与应用实际显示状态一致,避免逻辑判断错误:
func hideApp() { DispatchQueue.main.async { NSApplication.shared.hide(self) // 强制同步状态 self.appShown = NSApplication.shared.mainWindow?.isVisible == false } automaticScroller.updStatus(appDisplayed: appShown) }
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

