在做项目过程中,发现存在这样一个问题:NSTableView的cellView添加trackingArea后滚动,跟踪区域显示不正确。分析后得出结论:鼠标在滚动的同时离开trackingArea,不会调用mouseExited方法,因此会产生该错误。那么,当鼠标通过滚动或动画退出NStrackingArea时,为什么不调用mouseExited/ mouseenter ?
问题代码示例
- (void) awakeFromNib
{
[self createTrackingArea];
}
- (void) createTrackingArea
{
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
_trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:_trackingArea];
}
//页面滚动时调用
- (void)updateTrackingAreas
{
NSArray *trackings = [self trackingAreas];
for (NSTrackingArea *tracking in trackings)
{
[self removeTrackingArea:tracking];
}
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveAlways owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
//鼠标进入
-(void)mouseEntered:(NSEvent *)theEvent
{
NSLog(@"Mouse entered");
}
//鼠标退出
-(void)mouseExited:(NSEvent *)theEvent
{
NSLog(@"Mouse exited");
}
上述代码存在的问题
mouseEntered 和mouseExited方法只在鼠标有移动的时候调用。为了分析上述代码存在的问题,首先我们看下第一次添加NSTrackingArea的过程。
作为一个简单例子,首先我们创建一个view,该view只绘制了一个白色背景,但是如果用户将鼠标悬浮在该view上,它将绘制红色背景以示区别。该示例使用ARC模式。
@interface ExampleView
- (void) createTrackingArea
@property (nonatomic, retain) backgroundColor;
@property (nonatomic, retain) trackingArea;
@end
@implementation ExampleView
@synthesize backgroundColor;
@synthesize trackingArea
- (id) awakeFromNib
{
[self setBackgroundColor: [NSColor whiteColor]];
[self createTrackingArea];
}
- (void) createTrackingArea
{
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void) drawRect: (NSRect) rect
{
[[self backgroundColor] set];
NSRectFill(rect);
}
- (void) mouseEntered: (NSEvent*) theEvent
{
[self setBackgroundColor: [NSColor redColor]];
}
- (void) mouseEntered: (NSEvent*) theEvent
{
[self setBackgroundColor: [NSColor whiteColor]];
}
@end
上述代码有两个问题:
问题1
- 当
-awakeFromNib
方法调用时,如果鼠标已经在该view中,则-mouseEntered
方法就不会被调用。这意味着即使鼠标在view上方,但是view的背景一直为白色,而不会变成红色。这实际上在NSView文档的-addTrackingRect:owner:userData:assumeInside:
方法中提到了。关于assumeInside参数说明如下:
If
YES
, the first event will be generated when the cursor leaves*aRect*
, regardless if the cursor is inside*aRect*
when the tracking rectangle is added. IfNO
the first event will be generated when the cursor leaves*aRect*
if the cursor is initially inside*aRect*
, or when the cursor enters*aRect*
if the cursor is initially outside*aRect*
. You usually want to set this flag toNO
.
如果为YES,第一个事件将在光标离开aRect时生成,无论添加跟踪矩形时光标是否在aRect内。如果为NO,当光标离开aRect时(如果光标最初在aRect内部),或者当光标进入aRect时(如果光标最初在aRect外部),将生成第一个事件。您通常希望将此标志设置为NO。
上述两种情况下,如果鼠标已经在tracking area区域内,直到鼠标离开tracking area才会产生第一个事件。
因此,需要作出修改,当我们添加tracking area时,我们需要判断鼠标是否在tracking area区域内部。因此我们的 -createTrackingArea
方法改写为如下:
- (void) createTrackingArea
{
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
mouseLocation = [self convertPoint: mouseLocation
fromView: nil];
if (NSPointInRect(mouseLocation, [self bounds]))
{
[self mouseEntered: nil];
}
else
{
[self mouseExited: nil];
}
}
问题2
-
存在的第二个问题是滚动。当滚动或移动一个视图时,我们需要重新计算这个view的NSTrackingArea,该任务主要通过移除tracking area然后添加的方式解决。或许已经注意到,在滚动这个view的时候
-updateTrackingAreas
方法将会调用,这也是移除和重新添加tracking area的好地方:- (void) updateTrackingAreas { [self removeTrackingArea:trackingArea]; [self createTrackingArea]; [super updateTrackingAreas]; // Needed, according to the NSView documentation }
总结
通过上述步骤,该问题基本解决。诚然,每次添加跟踪区域时,都需要找到鼠标位置,然后将其转换为视图坐标,这很快就过时了,所以建议在NSView上创建一个自动处理这一问题的category。不会总是能够调用[self mouseenter: nil]或[self mouseExited: nil],所以可能想让类别接受几个块。一个在鼠标位于NSTrackingArea时运行,另一个在鼠标不在NSTrackingArea时运行。