質問のタイトルで述べたように、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
このコードには2つの問題があります。まず、-awakeFromNibが呼び出されたときに、マウスがすでにビュー内にある場合、-mouseEnteredは呼び出されません。これは、マウスがビューの上にある場合でも、背景が白のままであることを意味します。これは、NSViewのドキュメントで-addTrackingRect:owner:userData:assumeInsideのassumeInsideパラメーターについて実際に言及されています。
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];
}
}
2番目の問題はスクロールです。ビューをスクロールまたは移動するときは、そのビューのNSTrackingAreasを再計算する必要があります。これは、追跡領域を削除してから再度追加することで実行されます。前述のように、ビューをスクロールすると-updateTrackingAreasが呼び出されます。これは、領域を削除して再度追加する場所です。
- (void) updateTrackingAreas
{
[self removeTrackingArea:trackingArea];
[self createTrackingArea];
[super updateTrackingAreas]; // Needed, according to the NSView documentation
}
そして、それはあなたの問題の世話をする必要があります。確かに、追跡領域を追加するたびにマウスの位置を見つけてそれをビュー座標に変換する必要があるのはすぐに古くなるので、これを自動的に処理するカテゴリをNSViewに作成することをお勧めします。[self mouseEntered:nil]または[self mouseExited:nil]を常に呼び出すことができるとは限らないため、カテゴリに2、3ブロックを受け入れさせることができます。1つはマウスがNSTrackingAreaにある場合に実行し、もう1つはそうでない場合に実行します。