know when NSTextField becomes first responder - cocoa

How can I know when an NSTextField becomes the first responder (i.e. when the user click on it to activate it, but before they start typing). I tried controlTextDidBeginEditing but this doesn't get called until the user types the first character.

Subclass the NSTextField and overwrite the
- (BOOL)becomeFirstResponder
method, defined in the NSResponder class (a superclass of NSTextField), like so:
- (BOOL)becomeFirstResponder {
BOOL flag=[super becomeFirstResponder];
if(flag)
{
// text field will become first responder
}
return flag
}

Related

How to close window (NSWindowController) by hitting the ESC key?

Issue
I would like the user being able to close a window by hitting the ESC key but I can't get it to work in this specific case, hitting ESC triggers an error sound (the "no you can't do that" macOS bloop) and nothing happens.
Context
I'm making a subclass of NSWindowController which itself creates an instance of a subclass of NSViewController and sets it in a view. Both controllers have their own xib file.
NSWindowController:
final class MyWindowController: NSWindowController, NSWindowDelegate {
#IBOutlet weak var targetView: MainView!
let myVC: MyViewController!
var params: SomeParams!
override func windowDidLoad() {
super.windowDidLoad()
myVC = MyViewController(someParams: params)
myVC.view.setFrameSize(targetView.frame.size)
myVC.view.setBoundsSize(targetView.bounds.size)
targetView.addSubview(myVC.view)
}
override var windowNibName: String! {
return "MyWindowController"
}
convenience init(someParams params: SomeType) {
self.init(window: nil)
self.params = params
}
}
NSViewController:
final class MyViewController: NSViewController {
convenience init(someParams params: SomeType) {
// do stuff with the params
}
override func viewDidLoad() {
super.viewDidLoad()
// configure stuff for the window
}
}
What I've tried
I suppose that my issue is that the MyWindowController NSWindow is the .initialFirstResponder when I would want the content of the targetView (an NSTableView) to be the first responder - this way I could use keyDown, I guess, and send the close command to the window from there. This doesn't seem optimal, though.
I've tried forcing the view controller views into being the first responder by using window?.makeFirstResponder(theView) in the windowDidLoad of MyWindowController but nothing ever changes.
I've also tried adding this to MyWindowController:
override func cancelOperation(_ sender: Any?) {
print("yeah, let's close!")
}
But this only works if the user clicks first on the background of the window then hits ESC, and it still emits the error sound anyway. Which is actually what made me think that the issue was about the first responder being on the window.
Question
How would you achieve that? Of course, I know that the user can already close the window with CMD+W, but I'd really like to sort out this issue nonetheless.
Note that the code example is in Swift but I can also accept explanations using Objective-C.
The documentation of cancelOperation explains how cancelOperation should work:
This method is bound to the Escape and Command-. (period) keys. The key window first searches the view hierarchy for a view whose key equivalent is Escape or Command-., whichever was entered. If none of these views handles the key equivalent, the window sends a default action message of cancelOperation: to the first responder and from there the message travels up the responder chain.
If no responder in the responder chain implements cancelOperation:, the key window searches the view hierarchy for a view whose key equivalent is Escape (note that this may be redundant if the original key equivalent was Escape). If no such responder is found, then a cancel: action message is sent to the first responder in the responder chain that implements it.
NSResponder declares but does not implement this method.
NSWindow implements cancelOperation: and the next responder, the window controller, isn't checked for an implementation of cancelOperation:. The cancel: message does arrive at the window controller. Implementing
- (void)cancel:(id)sender
{
NSLog(#"cancel");
}
will work. The cancel: message isn't inherited from a superclass so autocompletion doesn't suggest it.
This worked for me in Xcode 10 and Swift 4.2:
#objc func cancel(_ sender: Any?) {
close()
}
I tried it before but without the #objc part and it didn't work. So don't omit it.
When I needed such behavior I implemented it by overriding keyDown: of the NSWindow object.
I.e. something like the following:
- (void)keyDown:(NSEvent *)theEvent
{
int k = [theEvent keyCode];
if (k == kVK_Escape)
{
[self close];
return;
}
[super keyDown:theEvent];
}

NSTextfield complete

It is there a solution to have a completion of a NSTextField with method :
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int*)index
with several words and not one? Because when you type a space, the completion start again...
Thanks.
Better late than never, it might be helpful for others:
It's a little tricky problem, since NSControlTextEditingDelegate / NSTextFieldDelegate doesn't offer a way to solve it directly. What you need to do is creating a custom subclass of NSTextView (yes, text view), and override the method - (NSRange)rangeForUserCompletion:
- (NSRange)rangeForUserCompletion
{
return [self selectedRange];
}
And then subclass NSTextFieldCell to override the method - (NSTextView *)fieldEditorForView::
- (NSTextView *)fieldEditorForView:(NSView *)aControlView
{
static MyTextView* _myFieldEditor = nil;
if (_myFieldEditor == nil) {
_myFieldEditor = [[MyTextView alloc] init];
[_myFieldEditor setFieldEditor:YES];
}
return _myFieldEditor;
}
Then in Interface Builder, set the class of your text field's cell to your subclass of NSTextFieldCell. What will happen is when your text field becomes first responder, the window will call your cell's -fieldEditorForView: method, and use your custom text view as the field editor. So during editing the value of your text field, any completion will call -(NSRange)rangeForUserCompletion on your text view.
Then you can fine tune your -rangeForUserCompletion to make it return the exact range you want for the completion.
Also, the code in fieldEditorForView: assumes that your app uses only one window, if you are using multiple windows (e.g. document-based apps), you'll have to change it and keep one field editor instance per window.
Hope it helps :)

mouseDown: in a custom NSTextField inside an NSTableView

I have a view-based NSTableView. Each view in the table has a custom text field.
I'd like to fire an action when the user clicks on the text field (label) inside the table's view (imagine having a hyperlink with a custom action in each table cell).
I've created a basic NSTextField subclass to catch mouse events. However, they only fire on the second click, not the first click.
I tried using an NSButton and that fires right away.
Here's the code for the custom label:
#implementation HyperlinkTextField
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"link mouse down");
}
- (void)mouseUp:(NSEvent *)theEvent {
NSLog(#"link mouse up");
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
return YES;
}
#end
Had the same problem. The accepted answer here didn't work for me. After much struggle, it magically worked when I selected "None" as against the default "Regular" with the other option being "Source List" for the "Highlight" option of the table view in IB!
Edit: The accepted answer turns out to be misleading as the method is to be overloaded for the table view and not for the text field as the answer suggests. It is given more clearly at https://stackoverflow.com/a/13579469/804616 but in any case, being more specific feels a bit hacky.
It turned out that NSTableView and NSOultineView handle the first responder status for NSTextField instances differently than for an NSButton.
The key to get the label to respond to the first click like a button is to overwrite [NSResponder validateProposedFirstResponder:forEvent:] to return YES in case of my custom text field class.
Documentation:
http://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html#//apple_ref/occ/instm/NSResponder/validateProposedFirstResponder:forEvent:
The behavior that you're seeing is because the table view is the first responder, which it should be or the row won't change when you click on the label -- this is the behavior that a user expects when clicking on a table row. Instead of subclassing the label, I think it would be better to subclass the table view and override mouseDown: there. After calling the super's implementation of mouseDown:, you can do a hit test to check that the user clicked over the label.
#implementation CustomTable
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown:theEvent];
NSPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
NSView *theView = [self hitTest:point];
if ([theView isKindOfClass:[NSTextField class]])
{
NSLog(#"%#",[(NSTextField *)theView stringValue]);
}
}
#end
In the exact same situation, embedding an NSButton with transparent set to true/YES worked for me.
class LinkButton: NSTextField {
var clickableButton:NSButton?
override func viewDidMoveToSuperview() {
let button = NSButton()
self.addSubview(button)
//setting constraints to cover the whole textfield area (I'm making use of SnapKit here, you should add the constraints your way or use frames
button.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(NSEdgeInsetsZero)
}
button.target = self
button.action = Selector("pressed:")
button.transparent = true
}
func pressed(sender:AnyObject) {
print("pressed")
}
You use window.makeFirstResponser(myTextfield) to begin editing the text field. You send this message from the override mouseDown(withEvent TheEvent:NSEvent) method

Execute an action from a text field ONLY when enter is pressed [duplicate]

I have two NSTextFields: textFieldUserID and textFieldPassword.
For textFieldPassword, I have a delegate as follows:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
This delegate gets called when textFieldPassword has focus and I hit the enter key. This is exactly what I want.
My problem is that controlTextDidEndEditing also gets called when textFieldPassword has focus and I move the focus to textFieldUserID (via mouse or tab key). This is NOT what I want.
I tried using controlTextDidChange notification (which is getting called once per key press) but I was unable to figure out how to detect enter key ( [textFieldPassword stringValue] does not include the enter key). Can someone please help me figure this one out?
I also tried to detect if textFieldUserID was a firstResponder, but it did not work for me. Here is the code I tried out:
if ( [[[self window] firstResponder] isKindOfClass:[NSTextView class]] &&
[[self window] fieldEditor:NO forObject:nil] != nil ) {
NSTextField *field = [[[self window] firstResponder] delegate];
if (field == textFieldUserID) {
// do something based upon first-responder status
NSLog(#"is true");
}
}
I sure could use some help here!
If I understood you correctly, you could set an action for the password text field and tell the field to send its action only when the user types Return. Firstly, declare and implement an action in the class responsible for the behaviour when the user types Return on the password field. For example:
#interface SomeClass …
- (IBAction)returnOnPasswordField:(id)sender;
#end
#implementation SomeClass
- (IBAction)returnOnPasswordField:(id)sender {
// do something
}
#end
Making the text field send its action on Return only, and linking the action to a given IBAction and target, can be done either in Interface Builder or programatically.
In Interface Builder, use the Attributes Inspector, choose Action: Sent on Enter Only, and then link the text field action to an IBAction in the object that implements it, potentially the File’s Owner or the First Responder.
If you’d rather do it programatically, then:
// Make the text field send its action only when Return is pressed
[passwordTextFieldCell setSendsActionOnEndEditing:NO];
// The action selector according to the action defined in SomeClass
[passwordTextFieldCell setAction:#selector(returnOnPasswordField:)];
// someObject is the object that implements the action
[passwordTextFieldCell setTarget:someObject];
[passwordTextFieldCell setTarget:self];
[passwordTextFieldCell setAction:#selector(someAction:)];
- (void) someAction{
//handle
}

NSTextField enter key detection or firstResponder detection

I have two NSTextFields: textFieldUserID and textFieldPassword.
For textFieldPassword, I have a delegate as follows:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
This delegate gets called when textFieldPassword has focus and I hit the enter key. This is exactly what I want.
My problem is that controlTextDidEndEditing also gets called when textFieldPassword has focus and I move the focus to textFieldUserID (via mouse or tab key). This is NOT what I want.
I tried using controlTextDidChange notification (which is getting called once per key press) but I was unable to figure out how to detect enter key ( [textFieldPassword stringValue] does not include the enter key). Can someone please help me figure this one out?
I also tried to detect if textFieldUserID was a firstResponder, but it did not work for me. Here is the code I tried out:
if ( [[[self window] firstResponder] isKindOfClass:[NSTextView class]] &&
[[self window] fieldEditor:NO forObject:nil] != nil ) {
NSTextField *field = [[[self window] firstResponder] delegate];
if (field == textFieldUserID) {
// do something based upon first-responder status
NSLog(#"is true");
}
}
I sure could use some help here!
If I understood you correctly, you could set an action for the password text field and tell the field to send its action only when the user types Return. Firstly, declare and implement an action in the class responsible for the behaviour when the user types Return on the password field. For example:
#interface SomeClass …
- (IBAction)returnOnPasswordField:(id)sender;
#end
#implementation SomeClass
- (IBAction)returnOnPasswordField:(id)sender {
// do something
}
#end
Making the text field send its action on Return only, and linking the action to a given IBAction and target, can be done either in Interface Builder or programatically.
In Interface Builder, use the Attributes Inspector, choose Action: Sent on Enter Only, and then link the text field action to an IBAction in the object that implements it, potentially the File’s Owner or the First Responder.
If you’d rather do it programatically, then:
// Make the text field send its action only when Return is pressed
[passwordTextFieldCell setSendsActionOnEndEditing:NO];
// The action selector according to the action defined in SomeClass
[passwordTextFieldCell setAction:#selector(returnOnPasswordField:)];
// someObject is the object that implements the action
[passwordTextFieldCell setTarget:someObject];
[passwordTextFieldCell setTarget:self];
[passwordTextFieldCell setAction:#selector(someAction:)];
- (void) someAction{
//handle
}

Resources