NSTableView添加NStrackingArea后滚动时跟踪区域显示不正确的问题分析及解决

在做项目过程中,发现存在这样一个问题: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. If NO 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 to NO.

如果为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时运行。

参考资料

mouseExited isn’t called when mouse leaves trackingArea while scrolling
(当鼠标在滚动时离开trackingArea时,不会调用mouseExited)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jarlen John

谢谢你给我一杯咖啡的温暖

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值