How can I limit a NSTextView to digits only? NSFormatter isn't available for NSTextView, and I couldn't find any options on the NSTextStorage/NSTextContainer either.
Create a subclass of NSTextView and override
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
Strip the non-digits from string and call super.
Override
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard type:(NSString *)type
to catch paste and drop.
You could respond to the delegate method
textView:shouldChangeTextInRange:replacementString: and then check if what is typed is a digit (use a characterSet for this). If it is a digit, return YES; if not, return NO.
Or maybe change the keyboard type to digits only.
Related
I have an NSTextField to collect a format string, which is used to create some text that is then saved to a file. If I enter double quotes (") in the NSTextField, the string from NSTextField.stringValue is encoded as 0xe2 0x80 0x9d, which is unicode for double quotes. If I try to save the resulting string to a file, using NSASCIIStringEncoding, it complains that the encoding fails. Can I force NSTextEdit to use the ASCII character for double quotes, 0x22? I've tried lossy conversion:
[NSString dataUsingEncoding: NSASCIIStringEncoding
allowLossyConversion: YES]
but its solution is to remove the quote altogether.
I've tried changing the "Use smart quotes and dashes" setting in System Preferences --> Keyboard --> Text
While an NSTextField is being edited, it has an NSTextView subview that handles the actual editing. This subview is called the “field editor”.
NSTextView has an automaticQuoteSubstitutionEnabled property. You want to turn this off for the field editor.
You can do this by creating a subclass of NSTextFieldCell and overriding the setUpFieldEditorAttributes method. In Swift:
class MyTextFieldCell: NSTextFieldCell {
override func setUpFieldEditorAttributes(_ textObj: NSText) -> NSText {
super.setUpFieldEditorAttributes(textObj)
if let textView = textObj as? NSTextView {
textView.isAutomaticQuoteSubstitutionEnabled = false
}
return textObj
}
}
In your xib, select the cell of your text field and set its custom class to MyTextFieldCell.
By default, each window has a single field editor shared by all text fields in the window. So using this custom cell subclass will have the effect of leaving smart quotes turned off for all text fields in that window after that one text field has had keyboard focus. If that's not what you want, then you need to use a separate field editor just for the one text field that you want to be free of smart quotes.
To use a separate field editor for the special text field, you have two choices:
Create an NSWindow subclass. In the subclass, override fieldEditor:forObject: to return a separate NSTextView for the fields where you want to disable smart quotes, and return [super fieldEditor:createFlag forObject:object] for normal field.
Give your window a delegate (if it doesn't have one already). In the delegate, implement windowWillReturnFieldEditor:toObject: to return a separate NSTextView if the field (the client argument) should have smart quotes disabled. Return nil for other fields.
If you go with a separate-field-editor solution, you probably don't have to use a custom NSTextFieldCell subclass. You can just set the special field editor's isAutomaticQuoteSubstitutionEnabled to false before returning it.
Intro
When my custom NSTextField is in "text editing mode" and the field editor has been placed in front of it as firstResponder I no longer get the keystrokes through NSTextField.keyDown(...). I understand that the keystrokes are now being routed through the field editor. Most online recommendations are to override the following method within the delegate of the custom NSTextField:
control(_:textView:doCommandBySelector:)
I have indeed overridden this method but it doesn't seem to get called? See code below:
class FocusDelegate: NSObject, NSTextFieldDelegate{
func control(control: NSControl, textView: NSTextView, doCommandBySelector commandSelector: Selector) -> Bool {
println("FocusDelegate")
if commandSelector == Selector("cancelOperation:"){
println("cancelOperation")
control.abortEditing()
return true
}
return false
}
}
The only other thing I do is set the delegate of my custom NSTextField to an instance of the class above. I have also tried placing the method above directly into the custom NSTextField class - it doesn't cause any errors but it also doesn't get called.
Questions
What am I missing above in getting the delegate call to work?
Other than creating my own custom field editor is this the only way? Can the textDidChange(notification: NSNotification) not be interrogated to yield the keys pressed?
In using the control(_:textView:doCommandBySelector:) delegate method, how do I trap the pressing of keys that do NOT have standard key bindings. In particular, I want to intercept the key combination "shift+enter" which does not map to any standard selector. (Implied Question: can you only map to standard key-action methods in NSResponder?)
I believe I have achieved greater clarity with regard to the workings of NSTextField its delegates and its fieldEditor and its delegates. Yes, each one has its own delegate and the NSTextField is automatically set up as the delegate of the fieldEditor.
I was adding a delegate to the host NSTextField. This will NOT get called when the fieldEditor is firstResponder, which was what I was concerned with. This is also not the important delegate for the scenario above.
What appears to be useful is making the NSTextField conform to the NSTextViewDelegate protocol and specifically overriding the method:
func textView(textView: NSTextView, shouldChangeTextInRange affectedCharRange: NSRange, replacementString: String) -> Bool
to trap various keypresses.
The notification in textDidChange(notification: NSNotification) is not of use in this scenario because it is reported AFTER the keypress.
Intercepting pressed keys in textView:shouldChangeTextInRange:replacementString: is not ideal because that is called rather late, and is also called when the user pastes from the clipboard, for instance.
A probably cleaner way is to intercept textView:doCommandBySelector:, which gets called for all kinds of special keys such as tab, cursor, return and enter, for which you'll then get specific selectors that let you determine which special key was invoked.
For some keys, however, such as Return (CR) and Enter (ETX), you can't tell them apart, because both use the same "insertNewline:" selector.
In this case, you can check whether the currently handled event is a keydown event, and if so, check which key it was.
Here's a quick test code that lets you monitor the selector names and the involved keycode:
-(BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
{
NSEvent *ev = self.view.window.currentEvent;
NSLog (#"selector: %s, key: %d", commandSelector, (int)ev.keyCode);
return NO;
}
When a view contains an NSTextField with the expansion tooltip enabled and the text doesn't fit, then the user hovers the cursor over the field, OS X shows an expansion tooltip. If you then call setStringValue: to change the text content of the NSTextField, the expansion tooltip's size is not updated. For instance, if the original text was 100 characters long but the new text is only 50 characters long, hovering over the new text will show an expansion tooltip large enough for 100 characters containing the new text. This is true even if the new string fits entirely in the NSTextField, which normally would prevent an expansion tooltip appearing.
The opposite also occurs: If the original string fits within the NSTextField, no expansion tooltip appears. If the new string does not fit within the NSTextField, no expansion tooltip appears even though it should.
Internally, the NSTextField's NSTextFieldCell implements the NSCell method expansionFrameWithFrame:inView:. Something (I'm not sure what) calls this once, and seems to cache the result. Setting a new string using setStringValue: does not cause this function to be called again.
Calling setNeedsDisplay on the NSTextField after calling setStringValue: does not fix this.
So how do I get AppKit to resize the expansion tooltip?
After a great deal of experimentation, I found two methods to fix this.
The very difficult way is to delete and recreate the entire NSTextField each time the text changes. This is laborious because NSTextField doesn't conform to the NSCopying protocol, so you have to use an NSArchiver and an NSUnarchiver to duplicate the original NSTextField, and even then some attributes are not copied, such as constraints.
The easy way is to hide and un-hide the NSTextField.
NSTextField *textField;
{...}
[textField setStringValue:newText];
[textField setHidden:YES];
[textField setHidden:NO];
This makes AppKit call expansionFrameWithFrame:inView: on the NSTextField's NSTextFieldCell, which properly updates the expansion tooltip's presence and size.
[textField resetCursorRects] seems to cause expansionFrameWithFrame:inView: to be called on the NSTextFieldCell as well. Tested only in macOS 10.14.
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?
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)