21

I need to find if the mouse position is inside an NSView's rect.

I'd use NSPointInRect(point, rect), but I'd need to convert the rect coordinates to screen coordinates and I'm not sure how. Any help would be much appreciated!

Arlen Anderson
  • 2,486
  • 2
  • 25
  • 36
  • Can you give us a reason why? Generally this technique isn't a great idea, but it's hard to say without knowing what you're writing. – Mike Abdullah Jan 08 '11 at 00:20

6 Answers6

34

Something like this should work for you:

NSView* myView; // The view you are converting coordinates to
NSPoint globalLocation = [ NSEvent mouseLocation ];
NSPoint windowLocation = [ [ myView window ] convertScreenToBase: globalLocation ];
NSPoint viewLocation = [ myView convertPoint: windowLocation fromView: nil ];
if( NSPointInRect( viewLocation, [ myView bounds ] ) ) {
    // Do your thing here
}

I don't personally use this many local variables but hopefully this make this example clearer.

Jon Steinmetz
  • 4,104
  • 1
  • 23
  • 21
  • NSEvent already has an [event locationInWindow] so the first 3 lines are redundant – Peter Lapisu Nov 24 '13 at 13:35
  • 2
    locationInWindow would probably work most of the time. But this is from Apple documentation: "With NSMouseMoved and possibly other events, the receiver can have a nil window (that is, window returns nil). In this case, locationInWindow returns the event location in screen coordinates." So it would appear that not relying on locationInWindow may be more robust. locationInWindow would certainly be the call to use if you already had an event. The method in my answer would work even if you did not already have an event object. – Jon Steinmetz Nov 24 '13 at 14:39
26

Use NSView's convertPoint:fromView: method with fromView as nil to covert a point in window coordinates to a view's coordinate system.

After converting the mouse point to the view's coordinate system, I would use NSView's mouse:inRect: method instead of NSPointInRect as the former also accounts for flipped coordinate systems.

Alternatively, you could use a Tracking Area Object.

Matt Bierner
  • 58,117
  • 21
  • 175
  • 206
  • 1
    NSTrackingArea was EXACTLY what i was looking for! Thank you! – Arlen Anderson Jan 08 '11 at 02:11
  • 1
    i am tying this... but the convertPoint function actually asks for an NSPoint object as its first parameter. What should i give. please ellaborate. – Izac Mac Mar 07 '12 at 21:20
  • 2
    I would like to add that you should pass `self.bounds` for the `NSRect` argument in the `NSView's mouse:inRect:` sending up the `self.frame` will offset the coordinates. – Coldsteel48 May 04 '16 at 05:03
7

The NSView isMousePoint function works for me, without having to worry about anything CG.

func isMouseInView(view: NSView) -> Bool? {
    if let window = view.window {
        return view.isMousePoint(window.mouseLocationOutsideOfEventStream, in: view.frame)
    }
    return nil
}
Ryan H
  • 1,656
  • 14
  • 15
5

Some code making use of mouse:inRect: (the recommended way; that accounts for flipped coordinates)

CGPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
CGRect rect = [self bounds];
if ([self mouse:point inRect:rect]) {
}
aepryus
  • 4,715
  • 5
  • 28
  • 41
1

None of these answers take into account that a view might not be rectangular.

Here is an universal method that works on non-rectangular views as well. Think watch-face or a complex path shape.

This code snippet will give you a localPoint from a globalPoint (aka globalToLocal). (GlobalPoint in 0,0 window space) If you change the argument to "toView" then you get a "localToGlobal" point.

let localPoint = convertPoint(aPoint, fromView: self.window?.contentView)    

The bellow code would return true or false if the point was inside the path or not.

CGPathContainsPoint(someCGPath,nil,localPoint,true)

NOTE: aPoint is a globalPoint derived from the parameter in hitTest, it can also be derived from:

(self.window?.mouseLocationOutsideOfEventStream)!

NOTE: There might be a more correct way of doing this. But this works,and doesn't care about flipped views.

Sentry.co
  • 5,355
  • 43
  • 38
  • There are still pitfalls even with the approach described above. I had a lot of trouble finding a clean solution for hitTesting the TextField inside an hierarchy of NSViews for instance. Since apple changes their tactics when it comes to hitTesting the textField you also have to adopt. I have found a way to get that working and its fairly clean. However ill probably start flipping and offsetting each hitTestPoint in each view in the hierarchy as this seems to be what apple does with their components. Just add it to a method that is included in every View class i use. – Sentry.co Feb 20 '16 at 08:16
  • This is a very "disorganised" article but it may give you some insight what not to try. http://stylekit.org/blog/2016/01/28/Hit-testing-sub-views/ (its basically all my notes from hitTesting research) Ive since moved on and have a clean workflow when it comes to hitTesting. – Sentry.co Feb 20 '16 at 08:18
0

It seems like many answers don't consider isFlipped flag. Here is a way that takes the view's isFlipped into account:

public extension NSView {

  /// Checks if the mouse cursor is in self, without an event.
  func containsMouseCursor() -> Bool {
    guard let window, let windowContentView = window.contentView else {
      return false
    }

    var mouseLocationInWindowContentView = window.mouseLocationOutsideOfEventStream // returns a coordinate in bottom-left origin coordinate system
    if windowContentView.isFlipped {
      // correct the coordinates if `contentView` is flipped
      mouseLocationInWindowContentView = CGPoint(mouseLocationInWindowContentView.x, windowContentView.frame.height - mouseLocationInWindowContentView.y)
    }
    // convert the cursor point into self's coordinate system
    let pointInSelf = windowContentView.convert(mouseLocationInWindowContentView, to: self)

    return isMousePoint(pointInSelf, in: bounds)
  }
}
Honghao Z
  • 1,419
  • 22
  • 29