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

macOS中如何实现QuickTime式窗口标题栏鼠标悬停淡入淡出效果?

实现QuickTime Player风格的标题栏淡入淡出效果(SwiftUI)

问题背景

我想实现类似QuickTime Player的交互效果:鼠标移入窗口时,标题栏及控件淡入;鼠标移出窗口时,标题栏及控件淡出。

最初尝试在SwiftUI的ContentView中添加onHover动作,通过AppDelegate获取窗口后切换以下设置:

window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
window.titlebarAppearsTransparent = true

该方案能实现基本的隐藏/显示,但无动画效果,且鼠标移至标题栏时会触发移出事件,导致标题栏反复隐藏显示,出现闪烁问题。

更新(12月9日周六14:05)

目前已实现基础效果,但仍存在两个问题:

  • 窗口调整大小时,跟踪矩形(tracking rect)无法自动更新;
  • 标题栏和控件的显示/隐藏没有淡入淡出动画。

当前使用的代码如下:

@Observable class AppDelegate: NSObject, NSApplicationDelegate {
    var window: NSWindow?
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        self.window = NSApplication.shared.windows.first
        
        if let contentView = window?.contentView {
            contentView.addTrackingRect(contentView.bounds, owner: self, userData: nil, assumeInside: false)
        }
    }
    
    @objc(mouseEntered:) func mouseEntered(with event: NSEvent) {
        self.window?.titleVisibility = .visible
        self.window?.titlebarAppearsTransparent = false
        self.window?.standardWindowButton(.closeButton)?.isHidden = false
        self.window?.standardWindowButton(.miniaturizeButton)?.isHidden = false
        self.window?.standardWindowButton(.zoomButton)?.isHidden = false
    }

    @objc(mouseExited:) func mouseExited(with event: NSEvent) {
        self.window?.titleVisibility = .hidden
        self.window?.titlebarAppearsTransparent = true
        self.window?.standardWindowButton(.closeButton)?.isHidden = true
        self.window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
        self.window?.standardWindowButton(.zoomButton)?.isHidden = true
    }
}

解决方案

1. 修复跟踪矩形不更新问题

当窗口大小改变时,需要先移除旧的跟踪矩形,再添加新的跟踪矩形。通过监听窗口的didResizeNotification实现自动更新:

@Observable class AppDelegate: NSObject, NSApplicationDelegate {
    var window: NSWindow?
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        self.window = NSApplication.shared.windows.first
        setupTrackingRect()
        // 监听窗口大小变化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(windowDidResize),
            name: NSWindow.didResizeNotification,
            object: window
        )
    }

    @objc private func windowDidResize() {
        setupTrackingRect()
    }

    private func setupTrackingRect() {
        guard let contentView = window?.contentView else { return }
        // 移除所有旧的跟踪矩形
        contentView.removeAllTrackingRects()
        // 添加新的跟踪矩形
        contentView.addTrackingRect(contentView.bounds, owner: self, userData: nil, assumeInside: false)
    }

    // 原mouseEntered、mouseExited方法保留,后续替换为带动画版本
}

2. 添加淡入淡出动画

直接修改isHidden属性无法实现平滑动画,需要通过修改控件的alphaValue,配合NSAnimationContext执行渐变动画:

@objc(mouseEntered:) func mouseEntered(with event: NSEvent) {
    NSAnimationContext.runAnimationGroup { context in
        context.duration = 0.2 // 动画时长可按需调整
        context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        
        window?.titleVisibility = .visible
        window?.standardWindowButton(.closeButton)?.animator().alphaValue = 1
        window?.standardWindowButton(.miniaturizeButton)?.animator().alphaValue = 1
        window?.standardWindowButton(.zoomButton)?.animator().alphaValue = 1
        window?.animator().titlebarAppearsTransparent = false
    }
}

@objc(mouseExited:) func mouseExited(with event: NSEvent) {
    NSAnimationContext.runAnimationGroup { context in
        context.duration = 0.2
        context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        
        window?.standardWindowButton(.closeButton)?.animator().alphaValue = 0
        window?.standardWindowButton(.miniaturizeButton)?.animator().alphaValue = 0
        window?.standardWindowButton(.zoomButton)?.animator().alphaValue = 0
        window?.animator().titlebarAppearsTransparent = true
    } completionHandler: { [weak self] in
        // 动画完成后再隐藏标题,避免中途闪烁
        self?.window?.titleVisibility = .hidden
    }
}

3. 解决标题栏触发移出事件的闪烁问题

之前的闪烁是因为鼠标移到标题栏时离开了contentView的跟踪范围,改用NSTrackingArea并设置跟踪整个窗口区域即可解决:

private func setupTrackingRect() {
    guard let window = window else { return }
    let trackingArea = NSTrackingArea(
        rect: window.frame,
        options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect],
        owner: self,
        userInfo: nil
    )
    window.contentView?.addTrackingArea(trackingArea)
}

内容的提问来源于stack exchange,提问作者Mark

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 18:10:29