3

In my macOS application, I have a custom WebView. When the user moves the mouse cursor over different elements in the WebView's webpage like text, links, etc. (which are implemented as subviews, I think), the cursor updates automatically, based on the type of the element.

My requirement is that I want to set a custom cursor for the WebView (even for my entire application if possible), and not let it change whatsoever, at least till I programmatically change it in some other part of the code. I tried to set the NSCursor in the AppDelegate's applicationDidFinishLaunching: method, but it gets instantly reset.

I have tried many solutions like setting an NSTrackingArea, sending disableCursorRects to both my NSApplication and NSWindow, and even monitoring cursor update events, without much success.

So is there a way to disable all NSCursor updates within my application? I would set it to my custom cursor in applicationDidFinishLaunching: and want it to remain the same as long as the application runs, or I change it in code.

Shiva Prasad
  • 106
  • 12

2 Answers2

1

You can use a user stylesheet in the WebView to set the default curser for html elements.

WebPreferences *preferences = [[[WebPreferences alloc] initWithIdentifier:@"TestIdentifier"] autorelease];

NSURL *stylesheetURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"yosemite-stylesheet" withExtension:@"css" subdirectory:@"Welcome"];

preferences.userStyleSheetEnabled = YES;
preferences.userStyleSheetLocation = stylesheetURL;


self.webView.preferences = preferences;
catlan
  • 25,100
  • 8
  • 67
  • 78
  • Thanks, but I have tried achieving this by editing HTML styles (via JS instead, using `stringByEvaluatingJavaScriptFromString:`). I found a lot of drawbacks to this approach, like it does not work when the WebView is loading, doesn't seem to work on `iframes`, etc. Is there a better way, without depending on the web content? – Shiva Prasad May 08 '17 at 17:12
  • A user stylesheet is different then trying to change it via JS. It is applied to all pages visited in the webview and as such also to all iframes. For the loading part I'm not sure... – catlan May 08 '17 at 17:43
  • Okay, but I feel this solution is still hacky. I can't get the desired behaviour if the custom stylesheets of the HTML page override the CSS `cursor` property. Also I would like the behaviour while the webView is loading. Still searching for a better solution. Thanks for trying to help. :) – Shiva Prasad May 09 '17 at 07:23
  • Did you read https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CursorMgmt/Tasks/ChangingCursors.html ? – catlan May 09 '17 at 12:17
  • Yup, I had read that more than once. Tried it and failed. But I finally used the same to figure out a better solution. See my answer above. :) – Shiva Prasad May 10 '17 at 08:08
1

Found a solution that worked for me! Here's how I did it:

Subclass NSView and override the resetCursorRects method, in which send addCursorRect:cursor: with its bounds as the cursor rect and the required cursor (described here). Then make an instance of this custom NSView with the same frame as the WebView, and add it to the later as a subview. This will act as a transparent overlay over the WebView. It affects only the cursor and does not interfere with mouse clicks or keypresses.

Here's the code in PyObjC (the concept should apply in Objective C as well):

# Overlay View (NSView subclass)
class OverLayView(AppKit.NSView):
   def resetCursorRects(self):
       self.addCursorRect_cursor_(self.bounds(), my_custom_cursor)

# In my main class
    ...
    self.overlay = OverLayView.alloc().initWithFrame_(self.webview.bounds())
    self.webview.addSubview_(self.overlay)
    ...

If I ever want to reset the cursor back to auto, I can just remove this overlay view, or even better, set it temporarily hidden by sending it the setHidden: message:

self.overlay.setHidden_(Foundation.YES)

Some additional things to note:

  • Using an NSTrackingArea instead of the cursor rect doesn't seem to work well. For me it didn't.
  • I had tried the above method on the custom WebView itself, but it didn't work. The WebView's default tracking areas/cursor rects always interfere. Adding a subview on top of it blocks this behaviour, as the above code does.
  • Finally the overlay view should be a subview of the WebView. If both are sibling views of the same superview, Cocoa doesn't guarantee which one will be on top of the other.

If someone has a better solution, or can improve this one, feel free to post it.

Hope this helps someone!

Shiva Prasad
  • 106
  • 12