How to disable mouse clicks and mouse drags on a NSView? - swift

I am making a mac app using Swift and this app has a custom view (a class extending NSView and overriding its draw method). Now, I want to disable all mouse clicks and mouse drags on this view and pass them on to the other applications running beneath my application.
I have tried the following ways (gleaned from Apple documentation and other SO questions) to disable clicks on my view and nothing worked for me so far:
1. Overriding hitTest inside my custom View class
override func hitTest(_ point: NSPoint) -> NSView? {
let view = super.hitTest(point)
return view == self ? nil : view
}
2. Overriding acceptsFirstMouse inside my custom View class
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return false
}
3. Overriding mouseDown in ViewController as well as in my custom View class
override func mouseDown(with event: NSEvent) {
// do nothing
}
4. Overriding mouseDragged in ViewController as well as in my custom View class
override func mouseDragged(with event: NSEvent) {
// do nothing
}
Am I missing something?

This isn't handled at the view level, it's handled at the window level. You can set the ignoresMouseEvents property of the window to true.
The issue is that the Window Server will only dispatch an event to a single process. So, once it has arrived in your app, it's not going to another. And there's no feasible way for your app to forward it along, either.

Related

Why keyDown method does not work in Cocoa macOS?

I want use keyDown method to see which key pressed in keyboard but it does not work also my computer makes sounds to tell the key even does not work.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func keyDown(with event: NSEvent) {
print(event)
}
}
Only the responders in the current responder chain get a shot at handling events like key events, and then only if some other responder in the chain doesn't handle it first. So, you'll need to make sure that your view controller is managing a view that's the keyboard focus when the event actually takes place, and that the focused view doesn't handle the event itself.

UIView pass through touches only if more than one finger?

My UIView already handles single-finger touches with gesture recognisers for tap and pan just fine.
However, I would like touches with two fingers to be passed through to the parent view, behind this view. (The parent is a WKWebView with a javascript generated map that I'd like the user to be able to pinch-zoom, even while this other view is in front. This works OK when the other view is not in front, but of course when the front view is there, it doesn't pass through the touches.)
I have tried to detect this using either of the following in the front view, but in both cases, allTouches is an empty set (zero touches):
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if let event = event {
print("\(event)")
if let touches = event.allTouches {
print("\(event.allTouches)")
if touches.count > 1 {
return false
}
}
}
return super.point(inside: point, with: event)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let event = event {
print("\(event)")
if let touches = event.allTouches {
print("\(event.allTouches)")
if touches.count > 1 {
return nil
}
}
}
return super.hitTest(point, with: event)
}
How can I continue to use my existing gesture recognisers, but pass through multi-finger touches to the superview?
More of a work-around than an actual answer, but here's what solved it for me...
Instead of the in-front child view covering all of the parent view with a transparent layer, I reduced it to only include the small area needed to actually present information and form fields to the user.
Then I changed the single-touch gestures to be added to the parent view, instead of to the child view.
So now NONE of the touches are captured by the child view (except for its form fields and buttons).
This is perhaps the better way to handle my particular situation anyhow. So I was probably asking the wrong question (X/Y issue!).

Mouse moved events during button mouse up event

I want to receive information about mouse move events during button click (mouse up)
I'm adding NSTrackingArea to view that I want to track mouse move and mouse dragged events on, but I still don't receive these events.
I assume that mouseDown in NSButton is blocking mouse events, so the only solution I come up with is overriding mouseDown function for NSButton and not calling super.mouseDown, but then I need to handle button selection manually and I'm not sure if this is right approach for this.
Is this a right solution for my problem? Will there be no problems? Is there a better solution?
Here is code for test, just add button to new project and assign TestButton class to it.
class TestButton: NSButton {
override func mouseDown(with event: NSEvent) {
print("Mouse up")
super.mouseDown(with: event) // After removing events works.
print("Mouse down")
}
}
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let trackingArea = NSTrackingArea(rect: view.bounds, options: [.activeAlways, .mouseMoved, .enabledDuringMouseDrag], owner: self, userInfo: nil)
view.addTrackingArea(trackingArea)
}
override func mouseMoved(with event: NSEvent) {
print("Mouse moved")
super.mouseMoved(with: event)
}
override func mouseDragged(with event: NSEvent) {
print("Mouse dragged")
super.mouseDragged(with: event)
}
}
I had a similar requirement for my project. So initially, I started out with NSButton just like you. But, it turned out to be more hectic than I thought. Handling the action and selected state where the main concerns I faced. You could proceed with NSButton if it actually meets up with your requirement. But, I eventually moved to NSView and customised it. So, few of my implementation for handling move and drag event implementation I've open-sourced here is the link.
The traditional way to do this is to use a custom subclass of NSButtonCell and override continueTracking(last:current:in:) (inherited from NSCell). Your override should generally call through to super and return what it returns, but you can do something else in addition, to respond to the mouse movements. The issue is that NSCell and its subclasses have been "soft deprecated" for a while.
That said, it would be very surprising to me as a user that anything other than the button would react to the mouse movements while I'm interacting with the button (clicked in it and dragging).

Swift: How to determine which control was clicked on mouseDown

I am trying to catch mouse down events on some of my controls in the (Cocoa with Storyboards) application window.
If I override mouseDown(with event: NSEvent) in my ViewController class, I face two issues:
It is not possible to identify (directly) which exactly control in the window has been clicked. To do so, I use the following:
Swift 4:
override func mouseDown(with event: NSEvent)
{
let point: NSPoint = event.locationInWindow
let view: NSView = self.view.hitTest(point)!
if type(of:view) == NSTextField.self // with tags is also fine
{
// do something with the control (in the example NSTextField)
// (view as! NSTextField).backgroundColor = NSColor.systemPink
}
}
I am a little bit puzzled, why for such a basic GUI operation, there isn't a "native" event handler, as provided for the mouse click (by creating an #IBAction).
Am I missing something, or this is the way to catch and handle the mouse down events?
For some controls, e.g. NSLevelIndicator, my overridden method is not called. Why?
You must override NSView mouseDown, not NSViewController mouseDown.

First responder on mouse down behavior NSControl and NSView

I have a custom control. If it inherits from NSView, it automatically becomes the first responder when I click on it. If it inherits from NSControl, it does not. This difference in behavior persists, even if I override mouseDown(with:) and don't call super.
Code:
class MyControl: NSView {
override var canBecomeKeyView: Bool { return true }
override var acceptsFirstResponder: Bool { return true }
override func drawFocusRingMask() { bounds.fill() }
override var focusRingMaskBounds: NSRect { return bounds }
override func draw(_ dirtyRect: NSRect) {
NSColor.white.set()
bounds.fill()
}
}
As you can see, I override acceptsFirstResponder among other methods and properties that are key view and responder related. I have also checked the refusesFirstResponder property. It is set to false.
What is the reason for this difference in behavior?
Is there a method or property that I can override to influence it?
Say I want the behavior where the view becomes the first responder when clicked and the view inherits from NSControl, is calling window!.makeFirstResponder(self) at the beginning of my mouse-down event handler a good solution or is there a better one?
The property to override is needsPanelToBecomeKey.
A Boolean value indicating whether the view needs its panel to become the key window before it can handle keyboard input and navigation.
The default value of this property is false. Subclasses can override this property and use their implementation to determine if the view requires its panel to become the key window so that it can handle keyboard input and navigation. Such a subclass should also override acceptsFirstResponder to return true.
This property is also used in keyboard navigation. It determines if a mouse click should give focus to a view—that is, make it the first responder). Some views (for example, text fields) want to receive the keyboard focus when you click in them. Other views (for example, buttons) receive focus only when you tab to them. You wouldn't want focus to shift from a textfield that has editing in progress simply because you clicked on a check box.
NSView returns true, NSControl returns false.