macOS开发 当滚动鼠标滚轮离开tracking区域的时候,mouseExited不被调用的问题
当滚动鼠标滚轮离开tracking区域的时候,mouseExited不被调用,这时候需要在自定义的视图中实现这个函数updateTrackingAreas:
- (void) updateTrackingAreas { [self removeTrackingArea:trackingArea]; [self createTrackingArea]; [super updateTrackingAreas]; // Needed, according to the NSView documentation }
- (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 (CGRectContainsPoint([self bounds], mouseLocation)) { [self mouseEntered: nil]; } else { [self mouseExited: nil]; } }
问:为什么当鼠标从NStrackingArea通过滚动或做动画退出时不调用mouseExited/ mouseenter ?
答:正如您在问题的标题中所注意到的,mouseenter和mouseExited仅在鼠标移动时调用。要了解为什么会这样,让我们首先看一下添加NSTrackingAreas的过程。
作为一个简单的例子,让我们创建一个通常绘制白色背景的视图,但如果用户将鼠标悬停在视图上,它将绘制红色背景。本例使用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
这段代码有两个问题。首先,当调用-awakeFromNib时,如果鼠标已经在视图中,则不会调用- mouseenter。这意味着即使鼠标在视图上方,背景仍然是白色的。这实际上是在NSView文档中提到的-addTrackingRect:owner:userData:assumeInside参数:
如果是,第一个事件将在光标离开aRect时生成,而不管添加跟踪矩形时光标是否在aRect内。如果不是,第一个事件将在光标离开时生成,如果光标最初在区域内,或者当光标进入区域时,如果光标最初在区域外。
在这两种情况下,如果鼠标在跟踪区域内,则在鼠标离开跟踪区域之前不会生成任何事件。
为了解决这个问题,当我们添加跟踪区域时,我们需要找出光标是否在跟踪区域内。我们的-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 (CGPointInRect(mouseLocation, [self bounds]) { [self mouseEntered: nil]; } else { [self mouseExited: nil]; } }
第二个问题是滚动。当滚动或移动视图时,我们需要重新计算该视图中的NSTrackingAreas。这是通过删除跟踪区域,然后将它们添加回来来完成的。正如您所注意到的,-updateTrackingAreas在滚动视图时被调用。这是移除和重新添加区域的地方。
- (void) updateTrackingAreas { [self removeTrackingArea:trackingArea]; [self createTrackingArea]; [super updateTrackingAreas]; // Needed, according to the NSView documentation }
这样你的问题就解决了。不可否认,每次你添加跟踪区域时都需要找到鼠标位置然后将其转换为视图坐标这很快就过时了,所以我建议在NSView上创建一个类别来自动处理这个。您并不总是能够调用[self mouseEntered: nil]或[self mouseExited: nil],因此您可能希望使类别接受几个块。如果鼠标在NSTrackingArea中,一个运行,如果不在,一个运行。
参考文章:https://blog.csdn.net/zhongyili_sohu/article/details/106492137?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-7-106492137-blog-6129958.235%5Ev36%5Epc_relevant_default_base3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-7-106492137-blog-6129958.235%5Ev36%5Epc_relevant_default_base3&utm_relevant_index=13