You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 22:53:06