5

I have a custom subclass of NSView in my app. I would like to know the exact point in the view, relative to it's origin, that was clicked with the mouse. (i.e. Not relative to the Window origin, but relative to the custom-view origin).

I have always used this, which has worked perfectly:

-(void)mouseDown:(NSEvent *)theEvent
{
    NSPoint screenPoint = [NSEvent mouseLocation];
    NSPoint windowPoint = [[self window] convertScreenToBase:screenPoint];
    NSPoint point = [self convertPoint:windowPoint fromView:nil];

    _pointInView = point;

    [self setNeedsDisplay:YES];
}

But now I get a warning that convertScreenToBase is deprecated and to use convertRectFromScreen instead. However I cannot get the same results from convertRectFromScreen, and anyway, I'm interested in a point, not a rect!

What should I use as the correct replacement for the deprecated code above? Thanks in advance!

Kenny
  • 1,083
  • 2
  • 8
  • 23

4 Answers4

6

This line from your code:

    NSPoint screenPoint = [NSEvent mouseLocation];

gets the location of the mouse cursor out of sync with the event stream. It's not the position of the event you're currently handling, which was a short time in the past; it's the position of the cursor right now, which means you're potentially skipping past some important stuff. You should almost always use the position in sync with the event stream.

To do that, use the theEvent parameter that your method receives. NSEvent has a locationInWindow property, which has already been translated to the coordinates of the window which receives it. That eliminates the need for you to convert it.

    NSPoint windowPoint = [theEvent locationInWindow];    

Your code to convert the window location to the view's coordinate system is fine.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
4

I found the solution:

NSPoint screenPoint = [NSEvent mouseLocation];
NSRect screenRect = CGRectMake(screenPoint.x, screenPoint.y, 1.0, 1.0);
NSRect baseRect = [self.window convertRectFromScreen:screenRect];
_pointInView = [self convertPoint:baseRect.origin fromView:nil];
Kenny
  • 1,083
  • 2
  • 8
  • 23
  • The only real difference from Max's answer seems to be using 1.0 instead of 0 for the rect size. – Kenny Jun 01 '15 at 11:44
3

I have made a sample project with a window and tested the 'old' and new scenario. The result is the same in both cases.

You have to make one additional step: Create a simple rect with the screenPoint as origin. Then use the origin of the new, returned rect.

Here is the new code:

-(void)mouseDown:(NSEvent *)theEvent
{
    NSPoint screenPoint = [NSEvent mouseLocation];
    NSRect rect = [[self window] convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 0, 0)];

    NSPoint windowPoint = rect.origin;
    NSPoint point = [self convertPoint:windowPoint fromView:nil];

    _pointInView = point;

    [self setNeedsDisplay:YES];
}

I hope I was able to help you!

mangerlahn
  • 4,746
  • 2
  • 26
  • 50
  • Hmmm. Your code and my code give completely different results! Also [rect origin] should be rect.origin since NSRect is not a class. – Kenny May 29 '15 at 13:31
  • You're right with rect.origin, that was my fault.. I will take another look at it! – mangerlahn May 30 '15 at 06:06
  • Where is the view located? It might be in a flipped coordinate system – mangerlahn May 30 '15 at 06:08
  • Thanks! The view is located some distance up from the bottom of the parent window, and some distance in from the left. It varies due to auto-layout and contraints. It is not flipped. (Origin = bottom left) – Kenny Jun 01 '15 at 08:30
1

Simply use convert(_:from:) can be inaccurate, this can happen when event's window and view's window are not the same. Please check my answer in another question for a more robust way.

https://stackoverflow.com/a/69784415/3164091

Honghao Z
  • 1,419
  • 22
  • 29