Why are Tracking Areas Not Respected - objective-c

I've been experimenting with tracking area, and having some problems, so I created this simple program as a test. I create one tracking area in the lower left corner of my view (which is the window's content view), but I receive mouseEntered and exited messages no matter where I enter or exit the view. I've also tried putting this code in the init method and awakeFromNib with the same results.
#implementation Parent //This view is the contentView of the main window
-(void)viewDidMoveToWindow{
NSLog(#"In viwDidMoveToWindow");
NSTrackingArea *area = [[NSTrackingArea alloc]initWithRect:NSMakeRect(0,0,50,50) options:NSTrackingInVisibleRect |NSTrackingMouseEnteredAndExited |NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area];
}
-(void)mouseEntered:(NSEvent *)theEvent {
NSLog(#"Entered");
}
-(void)mouseExited:(NSEvent *)theEvent {
NSLog(#"Exited");
}
#end
Why is the tracking area not being respected?

It has to do with the options you are using, try instead using
options:NSTrackingActiveAlways | NSTrackingMouseEnteredAndExited

Related

Cocoa NSOpenGLView - [win setDelegate:view ] shows error. How to delegate manually?

I'm programming in Eclipse (not Xcode) on Yosemita 10.10...
I try to catch MouseMoved event, but it not called (mouseDown, mouseDragged - works fine). So I'm using this example code from here
http://lists.apple.com/archives/mac-opengl/2003/Feb/msg00069.html
but compiller show error on
[app setDelegate: view];
(- cannot initialize a parameter of type 'id' with an lvalue of type 'NSView *')
If I comment this line - it's work, but mouseMoved don't calling.
Please help! I'm newbie in objective-c
OS X does not automatically track the mouse movement events for you unless you request them.
In order to receive mouseMoved: events, you should add an NSTrackingArea to your subclass of NSOpenGLView. For example:
- (void)awakeFromNib {
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:self.frame options:NSTrackingActiveAlways|NSTrackingMouseMoved owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
After that, your mouseMoved: method will be called.
- (void)mouseMoved:(NSEvent *)theEvent {
NSLog(#"moved");
}
You can optionally implement updateTrackingAreas if you need to update your tracking area manually when the view resizes. For details, please refer to Using Tracking-Area Objects.

When to override updateTrackingAreas

So I have a quite big NSScrollView with several custom views in it. In these custom views i'm overriding -(void)updateTrackingAreas like this:
- (void)updateTrackingAreas
{
[self removeTrackingArea:trackingArea];
trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:(NSTrackingCursorUpdate |
NSTrackingActiveWhenFirstResponder |
NSTrackingInVisibleRect)
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
But somehow I feel this is a little unefficient since I have about 50 instances of my custom view in the NSScrollView and only about 5 of them are visible at a time and this way I'm updating the tracking areas of non-visible views.
So I thought I would skip updating the tracking areas if the view is not in NSScrollView's visible rect. Something like:
- (void)updateTrackingAreas
{
if(!NSIntersectsRect([self frame], [[self superview] visibleRect]))
{
return;
}
// ...
}
This seems to be working out well but I'm not sure if this is safe.
Does anyone have some advice on this matter?

Access int variable from other class

I have two windows, my main window "window" and "help window" all inside my App Delegate. In my main window its view is subclassed and I want to draw a rect inside it. My help window has a rect also but it has an NSTracker on it. What I want to do is draw my rect in my window subclass with the x and y coordinates equal to my NSTracker position. The problem I am having is it crashes when I try to bring in the coordinates, any ideas of what I could be doing wrong? thanks
//My subclass of window is called CutoutView. This is all in draw rect
AppDelegate *controller = [[[NSApp delegate] window] contentView];
xValue = controller.mouseLoc.x;
yValue = controller.mouseLoc.y;
NSRectFillUsingOperation(NSMakeRect(xValue,yValue, 600, 400), NSCompositeClear);
[self update];
- (void)update
{
NSLog(#"test");
[self setNeedsDisplay:YES];
}
//My AppDelegate with the tracker helpView is a reference to the view of my second window "Help Window"
-(void)updateTrackingAreas
{
if(trackingArea != nil) {
[self.helpView removeTrackingArea:trackingArea];
[trackingArea release];
}
opts = (NSTrackingActiveAlways | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self.helpView bounds]
options:opts
owner:self
userInfo:nil];
[self.helpView addTrackingArea:trackingArea];
}
-(void)mouseMoved:(NSEvent *)theEvent
{
mouseLoc = [NSEvent mouseLocation];
NSLog(#"mouseMoved: %f %f", mouseLoc.x, mouseLoc.y);
}
in my CutoutView am i getting the AppController wrong because it is in a different window "helpWindow"? or does it have to do with my int values?
There are many things wrong with your code and it's obvious that you are fundamentally misunderstanding some basic concepts.
Firstly, you state that this code is in your drawRect: method;
AppDelegate *controller = [[[NSApp delegate] window] contentView];
xValue = controller.mouseLoc.x;
yValue = controller.mouseLoc.y;
NSRectFillUsingOperation(NSMakeRect(xValue,yValue, 600, 400), NSCompositeClear);
[self update];
There are several immediate flaws apparent. Firstly, why are you declaring controller to be of type AppController* when the method you are calling (-contentView) returns an NSView?
Your AppController is not a view (at least it should not be!), so you should be declaring the object as such:
NSView* mainView = [[[NSApp delegate] window] contentView];
If you are indeed using a view as a controller then this is completely wrong. See below for my note about MVC.
You don't specify where the mouseLoc property is coming from. We need to see where this is declared, because that will affect whether or not there are problems with it.
Your drawing code calls [self update], which simply tells the view to redraw itself. This will result in an infinite loop because every time the view draws it is forced to redraw. You should never call setNeedsDisplay: from inside drawRect:.
Even after making these changes, this code is very badly structured and the design is broken.
As it stands, your code violates the Model-View-Controller pattern. A views should not have knowledge of other views. You need to restructure things so that your views display properties of your controller without needing knowledge of other views. That means that you must store the mouse location in your controller (or a model object) and use some method for the view to access that information, preferably a datasource protocol or similar. In my answer to this other question I give an example of how to do that.
You need to read the Cocoa Drawing Guide. You also need to learn more core Cocoa concepts as it is clear you are misunderstanding how Cocoa is supposed to work.

Using NSTrackingArea in an NSView that is unattached to a window?

Basically, I want an "invisible" NSView covering my entire screen. I will add an NSTrackingArea to that, so that I get global mouse events as my cursor moves about the screen.
-(void)setTrackingArea
{
view = [[NSView alloc] initWithFrame:[NSScreen currentScreenForPoint:[NSEvent mouseLocation]].frame];
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:[NSScreen currentScreenForPoint:[NSEvent mouseLocation]].frame options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways owner:view userInfo:nil];
[view addTrackingArea:area];
[area release];
//[[window contentView] addSubview:view];
//I don't want to add the view to a window, as all tutorials say.
}
- (void)mouseExited:(NSEvent *)theEvent
{
NSLog(#"Exit"); //Never firing
}
Is this possible? Using NSViews and NSTracking Areas without a window?
Using an invisible view is definitely not something you want to do. Look into the addGlobalMonitorForEventsMatchingMask:: class method on NSEvent.
For example, here's how you would add a monitor for a movement of the mouse:
[NSEvent addGlobalMonitorForEventsMatchingMask:NSMouseMovedMask handler:^(NSEvent *mouseMovedEvent) {
//do something with that event
}];

mouseExited isn't called when mouse leaves trackingArea while scrolling

Why mouseExited/mouseEntered isn't called when mouse exits from NStrackingArea by scrolling or doing animation?
I create code like this:
Mouse entered and exited:
-(void)mouseEntered:(NSEvent *)theEvent {
NSLog(#"Mouse entered");
}
-(void)mouseExited:(NSEvent *)theEvent
{
NSLog(#"Mouse exited");
}
Tracking area:
-(void)updateTrackingAreas
{
if(trackingArea != nil) {
[self removeTrackingArea:trackingArea];
[trackingArea release];
}
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
More details:
I have added NSViews as subviews in NSScrollView's view. Each NSView have his own tracking area and when I scroll my scrollView and leave tracking area "mouseExited" isn't called but without scrolling everything works fine. Problem is that when I scroll "updateTrackingAreas" is called and I think this makes problems.
* Same problem with just NSView without adding it as subview so that's not a problem.
As you noted in the title of the question, mouseEntered and mouseExited are only called when the mouse moves. To see why this is the case, let's first look at the process of adding NSTrackingAreas for the first time.
As a simple example, let's create a view that normally draws a white background, but if the user hovers over the view, it draws a red background. This example uses 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
There are two problems with this code. First, when -awakeFromNib is called, if the mouse is already inside the view, -mouseEntered is not called. This means that the background will still be white, even though the mouse is over the view. This is actually mentioned in the NSView documentation for the assumeInside parameter of -addTrackingRect:owner:userData: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.
In both cases, if the mouse is inside the tracking area, no events will be generated until the mouse leaves the tracking area.
So to fix this, when we add the tracking area, we need to find out if the cursor is within in the tracking area. Our -createTrackingArea method thus becomes
- (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];
}
}
The second problem is scrolling. When scrolling or moving a view, we need to recalculate the NSTrackingAreas in that view. This is done by removing the tracking areas and then adding them back in. As you noted, -updateTrackingAreas is called when you scroll the view. This is the place to remove and re-add the area.
- (void) updateTrackingAreas
{
[self removeTrackingArea:trackingArea];
[self createTrackingArea];
[super updateTrackingAreas]; // Needed, according to the NSView documentation
}
And that should take care of your problem. Admittedly, needing to find the mouse location and then convert it to view coordinates every time you add a tracking area is something that gets old quickly, so I would recommend creating a category on NSView that handles this automatically. You won't always be able to call [self mouseEntered: nil] or [self mouseExited: nil], so you might want to make the category accept a couple blocks. One to run if the mouse is in the NSTrackingArea, and one to run if it is not.
#Michael offers a great answer, and solved my problem. But there is one thing,
if (CGRectContainsPoint([self bounds], mouseLocation))
{
[self mouseEntered: nil];
}
else
{
[self mouseExited: nil];
}
I found CGRectContainsPoint works in my box, not CGPointInRect,