7

Found a similar question to mine(this), but my issues seems to be a bit more associated with view hierarchy.

I have a NSTextView, then as sibling views, several other NSViews on top of it.

Similar to the question linked above, I setup a tracking area, and applied the cursor as such:

class CursorChangingView: NSView { override func updateTrackingAreas() { let trackingArea = NSTrackingArea(rect: } override func cursorUpdate(event: NSEvent) { NSCursor.arrowCursor().set() } } 

It does seem to work when hovering, but immediately goes back to the IBeam Cursor, which is the default cursor for NSTextViews under this CursorChangingView.

Is this the proper way of applying changing the cursor when hovering over a certain NSView, and is the NSTextView under it overriding my overrriding?

3 Answers 3

6

All you need is to subclass a custom view, override awakeFromNib method, add the custom tracking area for [.mouseMoved, .activeAlways] events: NSTrackingArea Info there. There is no need to override resetCursorRects and/or updateTrackingAreas. All you need is to override mouseMoved method and set the desired cursor there:

Note about discardCursorRects method:

From the docs

You need never invoke this method directly

Xcode 9 • Swift 4

import Cocoa class CursorChangingView: NSView { override func awakeFromNib() { addTrackingArea(NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseMoved], owner: self, userInfo: nil)) wantsLayer = true layer?.backgroundColor = NSColor.cyan.cgColor layer?.borderColor = NSColor.black.cgColor layer?.borderWidth = 1 } @objc override func mouseMoved(with theEvent: NSEvent) { NSCursor.pointingHand.set() } } 

Sample

Sign up to request clarification or add additional context in comments.

2 Comments

Did not work for me. If a textfield below the custom view has first responder, the cursor still changes back to the IBeam
Not sure what's changed, but your example project doesn't work in 2025. Thank you for making one though.
1

Thanks @Leo Dabus for your answer, but I managed to solve it, so I will post my answer too.

In my case, for some reason, mouseEntered and mouseEntered did not work at all.

So here is my code that finally got it to work:

class CursorChangingView: NSView { let trackingArea: NSTrackingArea? func setupTracking() { if self.trackingArea == nil { self.trackingArea = NSTrackingArea(rect: self.bounds, options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.CursorUpdate | NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveInActiveApp, owner: self, userInfo: nil) self.addTrackingArea(self.trackingArea!) } } override func updateTrackingAreas() { self.trackingArea = NSTrackingArea(rect: self.bounds, options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.CursorUpdate | NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveInActiveApp, owner: self, userInfo: nil) self.addTrackingArea(self.trackingArea!) } override func resetCursorRects() { self.discardCursorRects() self.addCursorRect(self.bounds, cursor: NSCursor.arrowCursor()) } override func mouseMoved(theEvent: NSEvent) { NSCursor.arrowCursor().set() } } 

It might be a little excessive, but worked, so will share this as my own solution.

2 Comments

In my case, this still flickers when the CursorChangingView is above an NSTextView. How did you get rid of that?
Also, the second time you're creating the tracking are, you're no longer tracking .mouseMoved
1

A few important notes:

  • Be careful calling super on your mouseMoved or similar events, or the cursor might just get reset by the base class implementation.
  • Only reset your tracking area when the parent view size changes; if you try to do this by overriding layout() it's going to be happening all the time which is not great

Here's an example class that you can just use as a base class in your storyboards.

Swift 4 code:

import Cocoa final class MouseTrackingTextView: NSTextView { // MARK: - Lifecycle override func awakeFromNib() { setupTrackingArea() } // MARK: - Resizing // Call this in your controller's `viewDidLayout` // so it only gets called when the view resizes func superviewResized() { resetTrackingArea() } // MARK: - Mouse Events override func resetCursorRects() { addCursorRect(bounds, cursor: cursorType) } override func mouseMoved(with event: NSEvent) { cursorType.set() } // MARK: - Private Properties private var currentTrackingArea: NSTrackingArea? private var cursorType: NSCursor { return isEditable ? .iBeam : .pointingHand } // MARK: - Private API private func setupTrackingArea() { let trackingArea = NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseMoved], owner: self, userInfo: nil) currentTrackingArea = trackingArea addTrackingArea(trackingArea) } private func resetTrackingArea() { if let trackingArea = currentTrackingArea { removeTrackingArea(trackingArea) } setupTrackingArea() } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.