Cocoa: Stopping the field editor from auto-completing on space key - cocoa

I have a custom view with several NSTextField controls for which I want to supply custom auto-completions and I have successfully implemented all that using the NSTextFieldDelegate Protocol. The auto-completions are full names or place names, depending on which text field is being edited.
The issue is that the auto-completions almost always contain a space character and so if the user is typing something that matches a suggestion, but doesn't want to accept that suggestion, the field editor will accept the suggestion when user presses the space key. I want the field editor to accept the suggestion using the tab key only.
I understand that this will involve subclassing NSTextView to provide a custom field editor, and this is documented by Apple as being the acceptable way, however it's not clear to me what methods I need to override and what the overridden methods need to do, in order to get me what I want.
Can anyone suggest how this is achieved?

I admit that I'd been fumbling with this question for quite some time earlier, before I discovered an acceptable answer via Google-fu. The magic code, blatantly stolen from the original answerer:
#interface MLFieldEditor : NSTextView #end
#implementation MLFieldEditor
- (void)insertCompletion:(NSString *)word forPartialWordRange:(NSRange)charRange movement:(NSInteger)movement isFinal:(BOOL)flag {
// suppress completion if user types a space
if (movement == NSRightTextMovement) return;
// show full replacements
if (charRange.location != 0) {
charRange.length += charRange.location;
charRange.location = 0;
}
[super insertCompletion:word forPartialWordRange:charRange movement:movement isFinal:flag];
if (movement == NSReturnTextMovement)
{
[[NSNotificationCenter defaultCenter] postNotificationName:MLSearchFieldAutocompleted object:self userInfo:nil];
} }
#end
(Additional reference)

Related

How do I get my NSTextView to respond properly to the Home and End keys?

Apple makes NSTextView respond to page up, page down, arrow keys, etc. automatically, but the home and end keys are not automatically handled by NSTextView out of the box. There's no apparent reason for this; I just logged a Radar on it. Until they fix that Radar, the question is: how do I make my NSTextView handle those keys correctly?
I just spent a little while googling around about this, and didn't find a good modern answer on either SO or elsewhere, so I'm posting my own answer here just for other's reference.
The wrong way to do this is to implement keyDown: and check for the particular keys having been pressed. This circumvents Apple's key-binding mechanism, which as it happens does supply the needed selectors for the concepts of "scroll to beginning" and "scroll to end"; NSTextView just doesn't respond to those selectors.
All you need to do is to add, in your NSTextView subclass, the following:
- (void)scrollToBeginningOfDocument:(id)sender
{
[self scrollRangeToVisible:NSMakeRange(0, 0)];
}
- (void)scrollToEndOfDocument:(id)sender
{
[self scrollRangeToVisible:NSMakeRange([[self string] length], 0)];
}
This hooks up the NSResponder methods for the relevant key bindings to appropriate actions in your NSTextView. These methods on NSResponder appear to have been public since 10.6 or so, and may have actually existed for a while before that, so this solution should be good on all modern systems.

NSTextField - notifications when individual keys are pressed

I am making an app that will add sound to keypresses as the user types in an NSTextField. I need to capture keystrokes and know what each individual keypress is (like "d" or "space" or "6"). The app depends on this. There is no other way around it.
Each window is an NSDocument File Owner, and it has a single NSTextField in it, which is where the document data is parsed, and the user will type.
After hours of parsing the Internet for answers and hacking away at code, the four most commonly repeated answers are:
"that is not how things work, here is (irrelevant answer)"
"you are new to Cocoa, that is a bad idea, use control:textView:doCommandSelector:" that doesn't give me individual keys, and some keys need their own unique sound trigger.
"use controlTextDidChange: or textView:shouldChangeTextInRange:replaceString:" controlTextDidChange doesn't give me individual keys, and the second one only works for textViews or UIKit.
People get confused and answer with recommendations for UIKit instead of AppKit, which is iOS-only.
The weird thing is that if I subclass NSTextField, it receives -keyUp. I don't know where -keyDown is going.
So my ultimate question is: can you tell me some kind of step-by-step way to actually capture the keyDown that is sent to NSTextField? Even if it's a hack. Even if it's a terrible idea.
I would love to solve this problem! I am very grateful for your reading.
controlTextDidChange is quite a good solution, but don't forget this 2 important things:
Set the delegate binding of the textField to the object where you define the controlTextDidChange method. Commonly, in document based apps it is the window controller, otherwise your app delegate.
Set the textField's control to "continous" in the attribute inspector section
If you miss those points, you will have no result.
This is a pretty old question, but as I was trying to implement a NSTextField that could react to keyDown so that I could create a hotkey preferences control I found I wanted the answer to this question.
Unfortunately this is a pretty non-standard use and I didn't find any places that had a direct answer, but I've come up with something that works after digging through the documentation (albeit in Swift 4) and I wanted to post it here in case it helps someone else with a non-standard use case.
This is largely based off of the information gleaned from the Cocoa Text Architecture Guide
There are three components to my solution:
Creating your NSWindowController and setting a NSWindowDelegate on your NSWindow:
guard let windowController = storyboard.instanciateController(withIdentifier:NSStoryboard.SceneIdentifier("SomeSceneIdentifier")) as? NSWindowController else {
fatalError("Error creating window controller");
}
if let viewController = windowController.contentViewController as? MyViewController {
windowController.window?.delegate=viewController;
}
Your NSWindowDelegate
class MyViewController: NSViewController, NSWindowDelegate {
// The TextField you want to capture keyDown on
var hotKeyTextField:NSTextField!;
// Your custom TextView which will handle keyDown
var hotKeySelectionFieldEditor:HotKeySelectionTextView = HotKeySelectionTextView();
func windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any? {
// If the client (NSTextField) requesting the field editor is the one you want to capture key events on, return the custom field editor. Otherwise, return nil and get the default field editor.
if let textField = client as? NSTextField, textField.identifier == hotKeyTextField.identifier {
return hotKeySelectionFieldEditor;
}
return nil;
}
}
Your custom TextView where you handle keyDown
class HotKeySelectionTextView: NSTextView {
public override func keyDown(with event: NSEvent) {
// Here you can capture the key presses and perhaps save state or communicate back to the ViewController with a delegate pattern if you prefer.
}
}
I fully admit that this feels like a workaround somewhat, but as I am experimenting with Swift at the moment and not quite up to speed with all of the best practices yet I can't make an authoritative claim as to the "Swift-i-ness" of this solution, only that it does allow a NSTextField to capture keyDown events indirectly while maintaining the rest of the NSTextField functionality.
Try like this if you print nslog you will get individual character record for example you pressd "A" you will get the same in console:-
-(void)controlTextDidChange:(NSNotification*)obj
{
NSLog(#"%#",[yourTextfield stringValue]);
}
Also, not sure this is only your requirement.
Text editing for an NSTextField is handled by an NSTextView provided by the window, called the field editor. See the NSWindow method fieldEditor:forObject: and the NSWindowDelegate method windowWillReturnFieldEditor:toObject:. I suppose you could use one of these to provide your own subclassed NSTextView as the field editor. Or, could you simply use NSTextView instead of NSTextField?

Programming for the iPad keyboard - a few questions

I think I've done my homework on this, but haven't found what I'm looking for.
I'm developing an iPad app and I'd like to be able to:
Watch for keypress events (if there are such things) and respond to them by:
altering what was entered and have the altered results appear in the UITextField (instead of what the user entered). This could be a macro, for example.
in some cases, I'd like to programmatically hit the shift button, so the next work the user enters begins with a capital letter.
I have seen in similar questions references to the UITextInput protocol, but I wasnt sure what I found there would be applicable here.
Any thoughts?
Thanks.
Example for UITextField
Connect the UITextField delegate to the app delegate, and add the following code:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if([string isEqual:#"b"]) {
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:#"!"];
return NO;
} else {
return YES;
}
}
Every time the user presses the 'b' key it shows an exclamation mark.
All other keys work as usual.
This way you can also change the case.
For example, when the user hits 'b', update the field with 'B'.
It's currently not possible to press the shift key by code.

Validate a user interface (NSButton) as user type in NSTextField

I know how I'd do this with NSTextView, but NSTextField doesn't seem to provide a way to access the NSTextStorage backing it, so I can't set myself as a delegate and process -textStorageDidProcessEditing:.
I have a NSTextField as part of a sheet. If that text field is emtpy at any point, the OK button should be disabled until some input is provided. That's basically all I'm looking to do, and I'm sure there's a really simple way to do this?
I tried:
[[[filenameInput cell] textStorage] setDelegate:self];
Thinking that NSTextFieldCell would provide the text storage (mostly since Xcode kindly auto-completed it for me) and then of course, did my validation via the delegate method:
-(void)textStorageDidProcessEditing:(NSNotification *)notification {
BOOL allowSubmit = ([[filenameInput stringValue] length] > 0)
&& (([relativePathSwitch state] == NSOnState) || ([[localPathInput stringValue] length] > 0));
[createButton setEnabled:allowSubmit];
}
This compiles but causes a runtime error as NSTextFieldCell does not respond to textStorage.
What's the standard pattern I should be following here? This must be one of those "every day" tasks for Cocoa devs, I imagine :)
This is what NSFormatter is for; create a subclass of NSFormatter and set an instance of it as your NSTextField's formatter, and let it validate the text the user enters.
Always the way... I found it. It's not listed on the page for NSTextFieldDelegate, or NSControlTextEditingDelegate, but on the page for NSControl itself.
The delegate method is: -controlTextDidChange:.

How to disable drag-n-drop for NSTextField?

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?

Resources