老谭笔记

OSX的MouseEntered和MouseExited事件检测

在OSX中检测鼠标移入移出事件不是特别方便,并且使用起来也不少坑,其实我们大多时候要求都是非常简单,就是希望鼠标移入移也时能正确响应,并且最终都能成对的调用,但事实又如何呢?
先来看看要实现mouseEntered和mouseExited应该怎么怎么做,通过查看NSView的文档可以看到,如果需要需要这两个事件,我们需要创建一个trakingArea的东西,有两个API可供使用:

1
2
3
4
5
6
7
8
9
10
11
[self addTrackingRect:self.bounds
owner:self
userData:nil
assumeInside:YES];
或者:
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways|NSTrackingAssumeInside;
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:self.bounds
options:options
owner:self
userInfo:nil];
[self addTrackingArea:area];

可能我们觉得在视图初使化时或awakeFromNib调用以上方法就万事大吉,但事实这样又错了,因为当大小或坐标改变后就会造成所指定的检测区域错误,所以我们需要重写updateTrackingAreas方法,将创建NSTrackingArea的工作放在其中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//判定鼠标是否在当前视图内
- (BOOL)mouseInView
{
if (!self.window)
return NO;
if (self.isHidden)
return NO;
NSPoint point = [NSEvent mouseLocation];
point = [self.window convertRectFromScreen:NSMakeRect(point.x, point.y, 0, 0)].origin;
point = [self convertPoint:point fromView:nil];
return NSPointInRect(point, self.visibleRect);
}
//self或者superView frame改变时均会调用此方法
- (void)updateTrackingAreas
{
[super updateTrackingAreas];
if ([self mouseInView])
{
[self mouseEntered:nil];
}else
{
[self mouseExited:nil];
}
[self removeTrackingArea:trackingArea];
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways|NSTrackingAssumeInside;
trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
options:options
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}

##坑1
我们发现当mouseEntered之后,如果此时视图隐藏,那么mouseExited事件将永远没有机会调用到了,即便是视图两次显示,所以我们需要添加以下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//self或者superView 隐藏或显示
- (void)viewDidHide
{
[super viewDidHide];
[self mouseExited:nil];
}
- (void)viewDidUnhide
{
[super viewDidUnhide];
if ([self mouseInView])
{
[self mouseEntered:nil];
}
}

##坑2
然后我们又发现当mouseEntered之后,如果此时self或superView被移除,那么mouseExited也不再有机会调用到,即便再次add回来,所以我又需要添加以下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//self从superView或window添加或移除
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
if (self.window)
{
if ([self mouseInView])
{
[self mouseEntered:nil];
}
}
else
{
[self mouseExited:nil];
}
}

##坑3
当检测到mouseEntered之后,如果此时视图所在窗口关闭或最小化时,mouseExited事件也有可能不被触发,经过测试发现,这与窗口的oneShot属性有关,当oneShot置为NO之后便能解决此问题,在窗口关闭或最小化时会自动触发mouseExited方法.