I have a macOS app with a view that occupies the entire window, with NSWindowStyleMaskFullSizeContentView
set within the window's styleMask
. I want to show the titlebar and window buttons when the mouse enters the window, and hide them when the mouse exits the window.
I am using a tracking area whose rect is window.contentView.bounds
, with the height of the titlebar subtracted from its height.
I have implemented the mouseEntered:
and mouseExited:
methods to handle the show/hide actions.
The problem I have is when I move the mouse to the window's titlebar, the app receives multiple mouseExited:
/mouseEntered:
events in rapid succession, causing a "fluttering" effect with the titlebar.
To reproduce this problem, I first created an Objective-C project in Xcode, and used IB to set the window property to 'Full Size Content View'. Then edited ViewController.m as shown:
//
// ViewController.m
// Repro
//
#import "ViewController.h"
@implementation ViewController
{
NSTrackingArea *trackingArea;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewWillAppear
{
// background color
self.view.wantsLayer = true;
self.view.layer.backgroundColor = [NSColor systemBlueColor].CGColor;
// tracking area
[self setupTrackingArea:self.view.window.contentView.bounds];
return;
}
- (void)setupTrackingArea:(CGRect)frame
{
if (trackingArea)
{
[self.view removeTrackingArea:trackingArea];
}
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect | NSTrackingMouseMoved;
// subtract the titlebar height from the content view height
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;
CGFloat titlebarHeight = windowHeight - contentHeight;
return titlebarHeight;
}
- (void)mouseEntered:(NSEvent *)event
{
[self showTitleBar];
[self showWindowButtons:self.view.window];
return;
}
- (void)mouseExited:(NSEvent *)event
{
[self hideTitleBar];
[self hideWindowButtons:self.view.window];
return;
}
- (void)showTitleBar
{
self.view.window.titlebarAppearsTransparent = false;
self.view.window.title = @"Repro";
return;
}
- (void)hideTitleBar
{
self.view.window.titlebarAppearsTransparent = true;
self.view.window.title = @"";
return;
}
- (void)hideWindowButtons:(NSWindow *)window
{
[window standardWindowButton:NSWindowZoomButton].hidden = true;
[window standardWindowButton:NSWindowMiniaturizeButton].hidden = true;
[window standardWindowButton:NSWindowCloseButton].hidden = true;
return;
}
- (void)showWindowButtons:(NSWindow *)window
{
[window standardWindowButton:NSWindowZoomButton].hidden = false;
[window standardWindowButton:NSWindowMiniaturizeButton].hidden = false;
[window standardWindowButton:NSWindowCloseButton].hidden = false;
return;
}
@end
Thanks to suggestions by Willeke (thanks so much for your help!), there is a solution. It involved subclassing NSView (I called it MyView), moving all of the tracking area handling into that view, and removing it from the ViewController. MyView.m is:
//
// MyView.m
// Repro
//
#import "MyView.h"
@implementation MyView
{
NSTrackingArea *trackingArea;
}
- (void)viewWillMoveToWindow:(NSWindow *)newWindow
{
if (newWindow)
{
newWindow.styleMask |= NSWindowStyleMaskFullSizeContentView;
[self setupTrackingArea:newWindow];
}
[super viewWillMoveToWindow:newWindow];
return;
}
- (void)updateTrackingAreas
{
[self setupTrackingArea:self.window];
[super updateTrackingAreas];
return;
}
- (void)setupTrackingArea:(NSWindow *)window
{
if (trackingArea)
[self removeTrackingArea:trackingArea];
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways;
trackingArea = [[NSTrackingArea alloc]initWithRect:self.bounds
options:options
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
return;
}
- (void)mouseEntered:(NSEvent *)event
{
[self showTitleBar];
[self showWindowButtons:self.window];
return;
}
- (void)mouseExited:(NSEvent *)event
{
[self hideTitleBar];
[self hideWindowButtons:self.window];
return;
}
- (void)showTitleBar
{
self.window.titlebarAppearsTransparent = false;
self.window.title = [NSProcessInfo processInfo].processName;
return;
}
- (void)hideTitleBar
{
self.window.titlebarAppearsTransparent = true;
self.window.title = @"";
return;
}
- (void)hideWindowButtons:(NSWindow *)window
{
// hide the minimize and zoom buttons (yellow and green)
[window standardWindowButton:NSWindowZoomButton].hidden = true;
[window standardWindowButton:NSWindowMiniaturizeButton].hidden = true;
[window standardWindowButton:NSWindowCloseButton].hidden = true;
return;
}
- (void)showWindowButtons:(NSWindow *)window
{
[window standardWindowButton:NSWindowZoomButton].hidden = false;
[window standardWindowButton:NSWindowMiniaturizeButton].hidden = false;
[window standardWindowButton:NSWindowCloseButton].hidden = false;
return;
}
@end