NSWindow全屏内容视图下mouseEntered/mouseExited事件抖动问题求助
解决macOS全屏内容视图窗口标题栏显示/隐藏时的抖动问题
问题根源
- 跟踪区域失效:切换标题栏显示状态时,窗口的
contentLayoutRect会发生变化,之前基于初始contentView.bounds设置的跟踪区域不再准确,鼠标移动时会反复触发进入/退出事件。 - 无状态控制:每次触发
mouseEntered:或mouseExited:都会强制执行显示/隐藏操作,即使当前状态已经符合预期,导致UI频繁刷新出现抖动。
修复代码
// // ViewController.m // Repro // #import "ViewController.h" @interface ViewController () @property (nonatomic, assign) BOOL isTitleBarVisible; @end @implementation ViewController { NSTrackingArea *trackingArea; } - (void)viewDidLoad { [super viewDidLoad]; self.isTitleBarVisible = NO; // 监听窗口布局变化,更新跟踪区域 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowLayoutChanged:) name:NSWindowDidResizeNotification object:self.view.window]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowLayoutChanged:) name:NSWindowDidMoveNotification object:self.view.window]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)viewWillAppear { // background color self.view.wantsLayer = true; self.view.layer.backgroundColor = [NSColor systemBlueColor].CGColor; // 初始化跟踪区域 [self setupTrackingArea:self.view.window.contentView.bounds]; // 初始隐藏标题栏 [self hideTitleBar]; [self hideWindowButtons:self.view.window]; } - (void)windowLayoutChanged:(NSNotification *)note { // 窗口布局变化时更新跟踪区域 [self setupTrackingArea:self.view.window.contentView.bounds]; } - (void)setupTrackingArea:(CGRect)frame { if (trackingArea) { [self.view removeTrackingArea:trackingArea]; } NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect; // 减去标题栏高度,确保跟踪区域只覆盖标题栏以下的内容区域 frame.size.height -= [self titlebarHeight:self.view.window]; trackingArea = [[NSTrackingArea alloc] initWithRect:frame options:options owner:self userInfo:nil]; [self.view addTrackingArea:trackingArea]; } - (CGFloat)titlebarHeight:(NSWindow *)window { CGFloat windowHeight = window.contentView.frame.size.height; CGFloat contentHeight = window.contentLayoutRect.size.height; return windowHeight - contentHeight; } - (void)mouseEntered:(NSEvent *)event { if (!self.isTitleBarVisible) { [self showTitleBar]; [self showWindowButtons:self.view.window]; self.isTitleBarVisible = YES; } } - (void)mouseExited:(NSEvent *)event { // 获取鼠标当前位置,判断是否在标题栏区域内 NSPoint globalPoint = event.locationInWindow; NSPoint localPoint = [self.view.window convertPoint:globalPoint toView:self.view.window.contentView]; CGFloat titleBarHeight = [self titlebarHeight:self.view.window]; if (localPoint.y >= self.view.window.contentView.bounds.size.height - titleBarHeight) { // 鼠标在标题栏区域,不隐藏 return; } if (self.isTitleBarVisible) { [self hideTitleBar]; [self hideWindowButtons:self.view.window]; self.isTitleBarVisible = NO; } } - (void)showTitleBar { self.view.window.titlebarAppearsTransparent = false; self.view.window.title = @"Repro"; } - (void)hideTitleBar { self.view.window.titlebarAppearsTransparent = true; self.view.window.title = @""; } - (void)hideWindowButtons:(NSWindow *)window { [window standardWindowButton:NSWindowZoomButton].hidden = true; [window standardWindowButton:NSWindowMiniaturizeButton].hidden = true; [window standardWindowButton:NSWindowCloseButton].hidden = true; } - (void)showWindowButtons:(NSWindow *)window { [window standardWindowButton:NSWindowZoomButton].hidden = false; [window standardWindowButton:NSWindowMiniaturizeButton].hidden = false; [window standardWindowButton:NSWindowCloseButton].hidden = false; } @end
关键修改点
- 状态变量
isTitleBarVisible:避免重复执行显示/隐藏操作,减少UI刷新次数。 - 监听窗口布局通知:在窗口大小变化、移动时重新计算跟踪区域,确保区域始终准确覆盖标题栏以下的内容区域。
- 优化
mouseExited:逻辑:判断鼠标是否处于标题栏区域,若是则不执行隐藏操作,防止鼠标停留在标题栏时触发不必要的隐藏。
内容的提问来源于stack exchange,提问作者user9276599
相关产品推荐
相关产品推荐

