I want to determine when my NSTextField subclass is in edit mode, i.e. when the cursor is present and ready to perform editing actions. I can detect when the text has actually been modified by using controlTextDidBeginEditing, but this is called only once the text has changed. Also I know when the field is potentially editable by using the editable property, but this is not the same as the field actually being in edit mode.
Related
While Undo for multiple NSTextViews in a window work persistently, the same is not the case for NSTextField controls by default.
I like to make Undo behave the same for NSTextField as it works with NSTextView.
I have so far found out that NSTextField uses its own private NSUndoManager, which does enable and handle the Undo/Redo menus autmatically, but without using the window's shared undoManager, and without marking the connected document dirty ("edited"): If I provide a private NSUndoManager instance to an NSTextField, it'll behave the same.
So I've tried, by overwriting undoManagerForTextView:, to have the NSTextFields in the window use the window's shared undoManager. That will indeed lead to the effect that the text fields share the same undo stack, and I can still issue an Undo after editing and then leaving a specific text field.
However, the problem is that it'll then crash, because it appears that the Undo gets applied to the wrong (i.e. the currently active) text field and not to the one the undo action was created from.
I've provided a simple example here: https://github.com/tempelmann/Undo_shared_NSTextFields
It's a freshly created document based app, with two text fields and two text views in the main window. To enable the shared undo manager for the text fields, I've created a subclass ("CustomTextField") of NSTextField that overrides the following function to provide the shared undo manager:
-(NSUndoManager *)undoManagerForTextView:(NSTextView *)view { // NSTextViewDelegate
return view.window.undoManager;
}
And the text fields in the storyboard are set to this subclass instead of NSTextField.
When run, it looks like this:
Now, if you edit the first and the second text view, and then use Undo twice, it'll undo the changes in each text view just fine.
But if you try the same with the two text fields, e.g. by replacing the longer preset texts in the fields with something shorter (such as "1" and "2"), you'll find that Undo replaces the text in the same active text field twice:
How do I make Undo in the text fields behave correctly, i.e. the same as the text views do?
If you rename a file in the Finder, the text field expands horizontally up to about the width of the column. And then it expands vertically up to three rows before scrolling. I'm assuming this has to be done in a text field outside the outline view. And I can get a text field to resize while I type. I just don't know how to place it over the outline view when necessary. And keep it pinned to the row if the outline view scrolls. Does anyone have any insights? Thanks!
Text editing is handled by a dedicated NSText and it’s called “field editor”. This shared single view is used for all text editing that happens in the window. It’s separate from what usually displays the text (when not editing).
Here are the docs:
https://developer.apple.com/documentation/appkit/nswindow/1419647-fieldeditor
As mentioned in the docs discussion section, you can use and customize another field editor. This should be a starting point for your task.
The window’s delegate can substitute a custom field editor in place of the window’s field editor by implementing windowWillReturnFieldEditor(_:to:). The custom field editor can become the default editor (common to all text-displaying objects) or specific to a particular text-displaying object (object).
NSControl docs also have a section about Field Editor that might help.
I am writing a Cocoa app which contains several NSTextFields. I am trying to propagate changes to the text in the text field to my model layer, but since I am registering an Undo action when the model is updated, this makes it a bit more complicated than simply listening for controlTextDidChange:.
If I hook onto the delegate method controlTextDidChange:, then it gets called once for every key pressed. If I update the model then, the model will treat each separate keypress as a separate Undo event. This is not desirable. What I want is to be notified when the default "Undo Typing" action is registered with the Undo manager, so I can update the model then. The NSTextField registers one (and only one) such event, when I pause typing for a moment. Unfortunately, I have no idea when this occurs.
I have tried listening for controlTextDidEndEditing:, and updating the model in that delegate method, but then I have to explicitly tab out of the text field before the model will update. Since I archive my model object directly in my NSDocument subclass, doing this will result in data loss for the user if they save the document after changing the contents of the text field, but don't tab out of the text field first, because the model would not have been updated in time. Therefore, this technique will not work.
I have also tried hooking onto the text field's field editor (during control:textShouldBeginEditing:) and listening for textDidEndEditing:, but then I find that this simply doesn't fire, so the model won't be updated, causing data loss as described in the previous paragraph. Can anyone help me figure this out? Thanks!
By default, Apple NSTextField leaves dangling edits as you mentioned.
Because of this problem, amongst others, I subclassed NSTextField and NSApplication to handle text field editing the way it should be handled: Pressing 'Enter', clicking outside the field, and other actions should end editing and produce the Undo entry.
Also, I highly recommend GCUndoManager replacement for NSUndoManager.
How to configure a view-based NSTableView to behave like so:
Rows are selectable
The user are unable to trigger edit mode by clicking a cell
Edit mode can be triggered by calling NSTableView-editColumn:row:withEvent:select: programmatically
The table view is dragged from the object library of Xcode interface builder, i.e., it uses an NSTableCellView (with an NSImageView and an NSTextField as its subviews) as the table view's cell view.
For view-based table views, -editColumn:row:withEvent:select: is relatively ineffective. It attempts to make the cell view the first responder for the window, but only certain views will accept first responder status. NSTableCellView does not, because it is not itself editable.
If you want to programmatically initiate editing in the text field within an NSTableCellView, you can do something like:
NSTableCellView* cellView = (NSTableCellView*)[tableView viewAtColumn:col row:row makeIfNecessary:YES];
if ([cellView.textField acceptsFirstResponder])
[cellView.window makeFirstResponder:cellView.textField];
To disable the user from starting editing through the UI, I think you will need to set the text field to not be editable. You would make it editable just before you initiate editing programmatically. For example, add a line cellView.textField.editable = YES; between the above two lines.
Then, you'll want to set it back to non-editable after editing ends. To do this, you can set the delegate of the text field to your controller object and implement -controlTextDidEndEditing:. Or, similarly, you can add an observer of the NSControlTextDidEndEditingNotification notification from the text field. Either way, when your code is called, you set the text field's editable property back to false. (If you don't otherwise have a reference to the text field in question, you can obtain it from the NSNotification's object property.)
I am having a devil of a time trying to figure out how to get the address of the Text Field Editor (NSTextView) of an NSCell—NSFormCell and NSTextFieldCell in particular? NSCell does not have a property to access it. I did figure out the editor is not allocated until one is actually editing the field.
I want to set the delegate so I can capture keystrokes for auto-completion.
By default, there's a single field editor for each window. Even if a control or cell uses a custom field editor, it's still vended by the window. You would call -[NSWindow fieldEditor:forObject:] to obtain the field editor for a given control.
However, the delegate of the field editor is always set to the control on whose behalf it is working. Setting the delegate to something else is likely to break things. So, you would typically use a custom subclass of the control and implement your delegate methods there.
Finally, controlling completions is normally done using -textView:completions:forPartialWordRange:indexOfSelectedItem: in the text view delegate, not by capturing keystrokes.