First responder on mouse down behavior NSControl and NSView - swift

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.

Related

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

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.

Can I observe an optional value in swift? If not, how might I go about attempting to observe a change?

I'm trying to observe a change to the selection of an NSPopUpButton in Swift 4. In my view controller's viewDidLoad() I've set up the observation token to observe the selectedItem property of the NSPopUpButton
override func viewDidLoad() {
super.viewDidLoad()
observation = observe(\.myPopUpButton.selectedItem) {
objectToObserve, change in
if change.kind == NSKeyValueObservedChange.Kind.setting {
// code to execute goes here
}
}
I set a breakpoint on the line where observation is set to determine that the token is being configured with the correct key path. I also set a break inside the closure to see when it is executed. When I change the selection of the NSPopUpButton, the closure does not execute.
selectedItem is of type, NSMenuItem?, so my suspicion is that I can't set an observation on an optional property. But I can't find anything in Apple's documentation that states whether or not that is the case and I'm not sure how I would go about verifying it for myself.
So I have sort of a primary question along w/ some followups:
Can I observe an optional property in Swift 4.1?
If so, how can I troubleshoot this, what am I doing wrong?
If not, how can I go about trying to monitor the state of the NSPopUpButton?
Troubleshoots that I've already tried...
added #objc dynamic to the my myPopUpButton declaration
Many properties of many AppKit objects are not KVO-compliant. Unless the documentation specifically says the property is compliant, you should assume it's not compliant. NSPopUpButton's selectedItem property is non-compliant.
The easiest way to be notified that the pop-up button's selected item changed is to set the button's target and action:
override func viewDidLoad() {
super.viewDidLoad()
myPopUpButton.target = self
myPopUpButton.action = #selector(popUpButtonDidFire(_:))
}
#IBAction private func popUpButtonDidFire(_ sender: Any) {
// code to execute goes here
}
Note that if you're creating the pop-up button in a storyboard or xib, you can wire it to the popUpButtonDidFire method by control-dragging from the pop-up button to the view controller.
As mentioned in the comments in macOS Cocoa Bindings and Swift's property observers are a very powerful way to observe values, even in prior Swift versions. An outlet is not needed.
Create a property and use the didSet observer
#objc dynamic var selectedObject : MyObject? {
didSet {
}
}
In Interface Builder in Bindings Inspector bind Selected Object to the target controller Model Key Path > selectedObject.
MyObject is the type of the represented object of the menu item. If nothing is selected selectedObject is nil. You can bind also Selected Index, Selected Tag or Selected Value (but not simultaneously).

Indicate that keyUp event has been handled in Swift?

In an NSTextView I am trying to make tab and shift-tab play a role in editing text, without tabs being inserted in the text. Currently I am detecting keypresses via NSTextViewDelegate's keyUp method:
override func keyUp(with event: NSEvent) {
if let currChar = event.characters {
if event.keyCode == 48 { // detect tab key
if event.modifierFlags.rawValue == 256 { // detect no modifier key
// do something
} else if event.modifierFlags.rawValue == 131330 { // detect shift modifier
// do another thing
}
}
}
I can't see anything in the documentation how to indicate to the NSTextView that I want it to ignore the tab key (I have tried the answer shown here, but tabs do not appear to trigger this event), or to prevent the event from moving up the responder chain.
I have also tried calling interpretKeyEvents([event]) at the beginning of my keyUp method, and overriding insertTab and insertBacktab. These are successfully called with the right keypresses, but a tab is still inserted into the text. The documentation seems to suggest it should prevent the tab being inserted:
It [keyDown] can pass the event to Cocoa’s text input management system by invoking the NSResponder method interpretKeyEvents:. The input management system checks the pressed key against entries in all relevant key-binding dictionaries and, if there is a match, sends a doCommandBySelector: message back to the view. Otherwise, it sends an insertText: message back to the view, and the view implements this method to extract and display the text. (emphasis mine)
The documentation talks about an event continuing up the responder chain if it has not been handled - how is this indicated? Is it important that I am using keyUp, not keyDown?
Yes, it matters that you’re overriding keyUp: instead of keyDown:. The key-down event happens before the key-up event, and NSTextView acts on the key-down event. By the time the system has called your keyUp: override, it’s too late to prevent the default handling of the key-down event.
Use custom subclass. If these methods are not being called it means the first responder is someone else and has eaten your event. As long as your textview is first responder your keyDown method will be called.
class MyTextView: NSTextView {
override func insertTab(_ sender: Any?) {
self.insertText("HELLO", replacementRange: NSMakeRange(0, 0))
//OR ANY CUSTOM TEXT EDITING, ACTION TO CHANGE FIRST RESPONDER...
}
override func insertBacktab(_ sender: Any?) {
self.insertText("AAAAA", replacementRange: NSMakeRange(0, 0))
//OR ANY CUSTOM TEXT EDITING, ACTION TO CHANGE FIRST RESPONDER...
}
}
Educational: "Key Event Handling in Cocoa Applications from WWDC 2010"

Get reference to current object

My goal is to safe a reference from the button, label or textfield inside a variable.
The problem is that I don't know on which control the user tapped.
I am having a simple application which looks like this:
The user can touch any control.
It is easy enough with just those three controls because I can drag in a action. But if I am having many of them I can't handle them all over the action methods. Is there a general way in which I can safe a reference to the control in a variable so that I can know which of the controls is the active one?
Edit
As suggested I am using a function and assigning the variable to the sender of the function. This is how it looks in code:
var currentObject: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextfield.action = #selector(myAction)
}
func myAction(sender: NSTextField)
{
print("aktuell: \(sender)")
currentObject = sender
}
As you can see this only works for a NSTextfield. Is there a way in which the function works for every control?
Set the tag attribute for each item, and then you can check sender.tag to identify which object is calling it.
To set the tag, select the Attributes inspector in Storyboard (upper right side - middle button of Utilities) and look for this section:

Select next NSTextField with Tab key in Swift

Is there a way to change the responder or select another textfield by pressing tab on the keyboard, in Swift?
Notes:
It's for a fill in the blank type application.
My VC creates a list of Words [Word], and each of those words has its own WordView - word.wordView. The WordView is what is displayed. WordView is a child of NSTextField.
I tried to override keydown but it doesn't allow me to type anything in the text view.
You have to connect your textField nextKeyView to the next textField through the IB or programmatically:
textField1.nextKeyView = textField2
Assuming you want to go from textView1 to textView2. First set the delegate:
self.textView1.delegate = self
Then implement the delegate method:
func textView(textView: NSTextView, doCommandBySelector commandSelector: Selector) -> Bool {
if commandSelector == "insertTab:" && textView == self.textView1 {
self.window.makeFirstResponder(self.textView2)
return true
}
return false
}
If you want some control over how your field tabs or moves with arrow keys between fields in Swift, you can add this to your delegate along with some move meaningful code to do the actual moving like move next by finding the control on the superview visibly displayed below or just to the right of the control and can accept focus.
public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
switch commandSelector {
case #selector(NSResponder.insertTab(_:)), #selector(NSResponder.moveDown(_:)):
// Move to the next field
Swift.print("Move next")
return true
case #selector(NSResponder.moveUp(_:)):
// Move to the previous field
Swift.print("Move previous")
return true
default:
return false
}
return false // I didn't do anything
}
I had the same or a similar problem, in that I wanted to use an NSTextView field, to allow multiple lines of text to be entered, but it was the sort of field where entering a tab character would make no sense. I found an easy fix for this: NSTextView has an instance property of isFieldEditor, which is set to false by default; simply set this to true, and tabs will now skip to the next field.