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

如何让NSWindow始终置于指定应用窗口下方且无闪烁?

实现macOS窗口始终置于目标窗口下方且保持可交互

核心思路

你的核心需求是窗口可交互(接收键盘、调整大小)但始终位于指定窗口下方,之前的方案失效是因为标题栏/窗口调整事件由NSWindow直接处理,自定义NSView无法拦截。需要从窗口层级控制和事件拦截两个层面入手,结合系统API和私有API实现。

可行解决方案

1. 自定义NSWindow子类拦截全局窗口事件

通过重写NSWindow的sendEvent、becomeKey、becomeMain方法,拦截所有可能触发窗口前置的操作,并强制维持层级:

class BackgroundStickyWindow: NSWindow {
    // 持有目标窗口的弱引用,避免循环引用
    weak var targetWindow: NSWindow?
    
    override func sendEvent(_ event: NSEvent) {
        // 拦截标题栏点击、窗口调整等可能触发前置的事件
        let eventTypes: [NSEvent.EventType] = [.leftMouseDown, .rightMouseDown, .otherMouseDown]
        if eventTypes.contains(event.type) {
            let hitArea = hitTest(event.locationInWindow)
            let shouldPreventOrdering = [
                .titlebar, .resizeLeft, .resizeRight, .resizeTop,
                .resizeBottom, .resizeTopLeft, .resizeTopRight,
                .resizeBottomLeft, .resizeBottomRight
            ].contains(hitArea)
            
            if shouldPreventOrdering {
                NSApplication.shared.preventWindowOrdering()
            }
        }
        
        super.sendEvent(event)
        
        // 事件处理后强制将窗口放回目标窗口下方
        targetWindow.map { self.order(.below, relativeTo: $0.windowNumber) }
    }
    
    override func becomeKey() {
        super.becomeKey()
        // 即使窗口成为key(接收键盘输入),也要维持层级
        targetWindow.map { self.order(.below, relativeTo: $0.windowNumber) }
    }
    
    override func becomeMain() {
        super.becomeMain()
        targetWindow.map { self.order(.below, relativeTo: $0.windowNumber) }
    }
}

2. 使用私有API减少层级调整闪烁

系统默认的order(.below)方法可能存在延迟,导致短暂闪烁。可以使用NSWindow的私有方法直接设置层级关系(注意:私有API会影响App Store审核,仅用于非上架场景):

// 在需要调整层级的位置替换order方法
if let targetWindow = targetWindow {
    // 私有API:_setOrderingMode:relativeToWindow:,参数1代表"below"
    self.perform(Selector(("_setOrderingMode:relativeToWindow:")), with: 1, with: targetWindow)
}

3. 确保窗口可交互的关键设置

在初始化窗口时,需要配置以下属性保证交互性:

let window = BackgroundStickyWindow(
    contentRect: NSRect(x: 0, y: 0, width: 400, height: 300),
    styleMask: [.titled, .resizable, .closable, .miniaturizable],
    backing: .buffered,
    defer: false
)
window.acceptsFirstMouse = true
window.makeFirstResponder(window.contentView)
// 绑定目标窗口,例如获取系统前置应用的主窗口
window.targetWindow = NSWorkspace.shared.frontmostApplication?.windows.first

关键说明

  • 标题栏/窗口调整事件由NSWindow直接处理,必须在sendEvent中拦截,自定义NSView无法覆盖这些区域的事件
  • 允许窗口成为keyWindow才能接收键盘输入,因此不能完全阻止becomeKey,而是在激活后立刻回调层级
  • 私有API的使用需要关闭Swift的严格类型检查(在Build Settings中关闭Swift Compiler - Strict Checking下的Implicitly Unwrapped Optionals)

内容的提问来源于stack exchange,提问作者George C.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:33:22