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
相关产品推荐
相关产品推荐

