How can I determine whether an NSSearchField/NSTextField has input focus?
The previous answer is wrong, because NSTextField / NSSearchField do not themselves become the first responder and handle edited text. Instead, they use the window's field editor, which is an NSTextView that is shared between all fields on the window (since only one of them can have focus at a time).
You need to see if the first responder is an NSText, and if so, if the search field / text field is its delegate.
NSResponder *firstResponder = [[NSApp keyWindow] firstResponder];
if ([firstResponder isKindOfClass:[NSText class]] && [(id)firstResponder delegate] == mySearchField) {
NSLog(#"Yup.");
}
While Greg Titus' answer probably works, I think the following is a better way:
BOOL isFirstResponder = mySearchField.currentEditor == mySearchField.window.firstResponder;
AppKit uses a “field editor” (which is an NSTextView) to handle the actual editing in an NSTextField (or an NSSearchField or an NSSecureTextField). While your text field has keyboard focus for its window, it has the field editor as a subview, and the window's first responder is the field editor.
So in general, you can check whether the text field has a field editor:
if textField.currentEditor() != nil {
// textField has the keyboard focus
} else {
// textField does not have the keyboard focus
}
However, when you move the focus out of the text field (by pressing the tab key or clicking in another text field), the text field posts an NSControl.textDidEndEditingNotification (Objective-C: NSControlTextDidEndEditingNotification). If the text field has a delegate, and the delegate implements the control(_:controlTextDidEndEditing:) method of the NSControlTextEditingDelegate protocol, then the delegate method is also called for the notification.
While this notification is being delivered (including calling the delegate method), the text field still has the field editor as a subview, and the field editor's delegate is still set to the text field. So if you don't want to consider the text field to still have the keyboard focus while in the notification handler (or delegate method), then testing the field editor will give the wrong answer.
(You might think this is a weird thing to test, because after all AppKit is sending you a notification that the text field is no longer the keyboard focus, so why do you need to ask? But maybe your notification handler calls into some other method that wants to check, and you don't want to have to pass in a flag saying “oh by the way the text field is/isn't the keyboard focus right now”.)
Okay, so anyway, before sending the NSControl.textDidEndEditingNotification, AppKit changes the text field's window's first responder. So you can check whether the text field has a field editor, and whether that field editor is the first responder of its window. While you are handling the NSControl.textDidEndEditingNotification, this test will report that the text field doesn't have the keyboard focus.
extension NSTextField {
public var hasKeyboardFocus: Bool {
guard
let editor = currentEditor(),
editor == window?.firstResponder
else { return false }
return true
}
}
Related
I have a Cocoa app that shows a "quick search" window similar to Spotlight. The window contains a visual effect view and inside a NSTextField. The text field stretches across the full width of the window.
I would like to be able to move the window by dragging inside the empty area of the text field. When dragging across text in the text field, the normal editing (i.e. selection) behavior should be used instead.
In theory, moving a window by its background is easy:
window.isMovableByWindowBackground = true
However, this behavior does not work with NSTextField, because it intercepts dragging and attempts to select text instead.
Spotlight does it somehow. Here's an example:
A couple of options that I considered without success:
Tried overriding hitTest: returning nil
Tried overriding mouseDown|Up|Dragging: and forwarding to superview
Tried to use autolayout to have text field shrink to tightly wrap around its text (could not figure this one out)
For reference, I finally found a way:
Part 1: get NSTextField to grow/shrink with its content
Override intrinsicContentSize and measure its content:
private func measure(_ string:NSAttributedString) -> NSSize
{
let cell = NSTextFieldCell(textCell: stringValue)
cell.attributedStringValue = string
return cell.cellSize
}
Part 2: view setup
Add a placeholder view right after the text field
Set up auto layout to have the placeholder view to grow and shrink
Part3: all about the details
Set up the placeholder view to use the iBeam cursor to make it appear like a text field
If the user clicks in the placeholder view, make the text field the first responder
That's it.
I have a view based NSTableView with some labels in my customized and subclassed view. One of the label should be editable, so therefore I set this NSTextField to editable.
But now I have two problems, I can't solve:
1) If I move the mouse over the editable NSTextField, the cursor don't change to the IBeamCursor (the edit cursor).
2) I need to double click at the label, to be able to edit. I want to have a single click. I found some solutions for this problem here at stackoverflow, the best one is to override the acceptsFirstResponder to return always true, but then, clicking at the NSTextField selects the whole text instead of placing the cursor at the clicked position.
Sorry... this is a duplicate. I found this:
NSTableView - select row and respond to mouse events immediately
You have to subclass NSTableView. My swift code:
class TableViewEditing: NSTableView {
...
override func validateProposedFirstResponder(responder: NSResponder, forEvent event: NSEvent?) -> Bool {
return true
}
}
EDIT:
Just one disadvantage: Sometimes entering the edit mode, it seems that the text is just shortly selected and deselected. But you can see, that this is a cocoa problem, it's the same for example in Apple reminders app.
I have a window with two NSButtons and an NSTextField along with several views and several other controls. I assign the right and left arrows keys to each of the two NSButtons. The two buttons respond to the right and left arrow keys. However, when in the NSTextField, I want the arrow keys to perform as the normally would in a text field and not trigger the NSButtons. I have tried reading through the Cocoa Key Handling documentation and other questions regarding key events, but I could not find an example of trying change the key equivalent behavior in one control. I tried subclassing the NSTextField but couldn't trap the arrow keys. How can this be implemented?
You can override becomeFirstResponder: and in your implementation call setKeyEquivalent:. If you want to remove the key equivalent when the button loses first responder status, override resignFirstResponder:.
Do this in the control whose first-responder status you want to affect the button's equivalent. For example, if you have a view as a container and it can become first responder, you'd override -becomeFirstResponder: (calling super) then manage the button's equivalent there. If you don't yet understand these topics, you have a lot of prerequisite reading to do because a simple answer isn't possible here.
You could subclass NSButton and override performKeyEquivalent: like so:
override func performKeyEquivalent(event: NSEvent) -> Bool {
guard
let window = window where window.firstResponder.isKindOfClass(NSText.self) == false
else {
return false
}
return super.performKeyEquivalent(event)
}
This essentially disables the key equivalent if the first responder is a text field/view.
I'm working on an app that presents an NSPopover containing a number of NSTextFields. While I can tab between these fields as I expect, the popover is selecting a particular text field to be in the editing state when it appears, and it's not the field I want to edit; I'd like to be able to define which text field is editing on popover appearance programmatically (or in Interface Builder). How can I do this?
I've set up the appropriate key view loop by connecting IB outlets for all the various text fields involved, and I've hooked up the popover's nextResponder property to the text field I want to edit first, but that doesn't seem to have an effect - the popover will still select its preferred text field instead of mine. The Window Programming Guide suggests that I set the initialFirstResponder outlet of the window to the view I want selected, but an NSPopover is not an NSWindow and has no initialFirstResponder property (unless I'm missing something obvious).
Is there any way to specify which NSTextField I want to be editing when an NSPopover appears?
I think you said you tried using -makeFirstResponder: and passing the text field. This will set the window's firstResponder, but that's not the same as initialFirstResponder and the window must have initialFirstResponder set to something other than nil in order to respect the key view loop. (Source) A slight tweak to what you tried worked for me:
- (void)popoverWillShow:(NSNotification *)notification
{
// Set the window's initialFirstResponder so that the key view loop isn't auto-recalculated.
[[myField window] setInitialFirstResponder:myField];
}
I think you can make this work by setting all the text field's that you don't want to have focus to "selectable" instead of "Editable" in IB, this should leave the one text field you want to start with as the first responder. Then, in your popoverDidShow: method, set them all back to editable, and you should be able to tab between them as usual.
I want to disallow dropping anything into my NSTextField. In my app, users can drag and drop iCal events into a different part of the GUI. Now I've had a test user who accidentally dropped the iCal event into the text field – but he didn't realize this because the text is inserted in the lines above the one that I see in my one-line text field.
(You can reveal the inserted text by clicking into the text field and using the keyboard to go one line up – but a normal user wouldn't do this because he/she wouldn't even have realized that something got inserted in the first place!)
I tried registerForDraggedTypes:[NSArray array]] (doesn't seem to have any effect) as well as implementing the draggingEntered: delegate method returning NSDragOperationNone (the delegate method isn't even invoked).
Any ideas?
EDIT: Of course dropping something onto an NSTextField only works when it has focus, as described by ssp in his blog and in the comments to a blog entry by Daniel Jalkut.
I am glad you discovered the comments in my blog post. I think they are the tip of the iceberg to discovering how to achieve what you're looking for.
You need to keep in mind that the reason dragging to an NSTextField works when it has focus, is that the NSTextField has itself been temporarily obscured by a richer, more powerful view (an NSTextView), which is called the "Field Editor."
Check out this section of Apple's documentation on the field editor:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextEditing/Tasks/FieldEditor.html
To achieve what you're striving for, I think you might need to intercept the standard provision of the field editor for your NSTextFields, by implementing the window delegate method:
windowWillReturnFieldEditor:toObject:
This gives you the opportunity to either tweak the configuration on the NSTextView, or provide a completely new field editor object.
In the worst case scenario, you could provide your own NSTextView subclass as the field editor, which was designed to reject all drags.
This might work: If you subclass NSTextView and implement -acceptableDragTypes to return nil, then the text view will be disabled as a drag destination. I also had to implement the NSDraggingDestination methods -draggingEntered: and -draggingUpdated: to return NSDragOperationNone.
#implementation NoDragTextView
- (NSArray *)acceptableDragTypes
{
return nil;
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
return NSDragOperationNone;
}
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
return NSDragOperationNone;
}
#end
I was able to solve this problem by creating a custom NSTextView and implementing the enter and exit NSDraggingDestination protocol methods to set the NSTextView to hidden. Once the text field is hidden the superview will be able to catch the drag/drop events, or if the superview doesn't implement or want the drag/drop they are discarded
For example:
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
//hide so that the drop event falls through into superview's drag/drop view
[self setHidden:YES];
return NSDragOperationNone;
}
- (void)draggingExited:(id<NSDraggingInfo>)sender {
//show our field editor again since dragging is all over with
[self setHidden:NO];
}
Have you tried - (void)unregisterDraggedTypes from NSView?