Как вы отметили в заголовке вопроса, mouseEntered и 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, если мышь уже находится внутри представления, -mouseEntered не вызывается.Это означает, что фон по-прежнему будет белым, даже если мышь находится над видом.На самом деле это упоминается в документации NSView для параметра acceptInside -addTrackingRect: owner: userData: acceptInside:
Если YES, первое событие будет сгенерировано, когда курсор покинет aRect, независимо от того, находится ли курсорнаходится внутри aRect, когда добавлен отслеживающий прямоугольник.Если NO, первое событие будет сгенерировано, когда курсор покинет aRect, если курсор изначально находится внутри aRect, или когда курсор войдет в 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 (NSPointInRect(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, а другой для запуска, если это не так.