NSTextField delegate notifications -- how to get text? - objective-c

I've been trying to learn to use Xcode, but I'm getting confused with how to register that NSTextField has changed. Essentially, I have an NSTextField and a button. Clicking the button does stuff with the text in the field. However, I want to be able to get the text of the field without needing to use the text field "Action:send on end editing." That is, I want to be able to enter text and immediately press the button, without hitting enter or tabbing out of the text box. It seems like the way to do this would be by setting a delegate for my NSTextField that responds to
- (void)controlTextDidChange:(NSNotification *)aNotification
But I don't understand how to get the text that has been entered. I assume it has something to do with
[[aNotification userInfo] valueForKey:#"NSFieldEditor"];
but I really have no idea where to go from there.

You're on the right track! The object that you get out of the notification's user info dictionary is the Field Editor, which is simply an NSTextView that's handling the text input on the text field's behalf.
Once you have that object, all you have to do is ask it for its textStorage, which is an NSTextStorage* object holding the text. That object, in turn, has its string which is a plain old NSString holding just the characters.
NSTextView * fieldEditor = [[aNotification userInfo] objectForKey:#"NSFieldEditor"];
NSString * theString = [[fieldEditor textStorage] string];
*A subclass of NSAttributedString, which is an object holding a string and associated "attributes" like color, font, and underlining.

In your button action method, simply read the current string value in the text field:
- (IBAction)didClickTheButton:(id)sender {
NSString* theString = [myTextField stringValue];
// do something with theString
}

If you're only ever handling a single text field, this may be simpler:
- (void)controlTextDidChange:(NSNotification *)obj {
[self.inputField stringValue];
}
I'm totally ignoring all the complicated details of NSText and whatnot and just using the simplicity of the notification being sent and the simplicity of getting the string value from a text field.

Related

Special auto-completion behaviour for NSComboBox

I have an NSComboBox containing several items. If the user types while the list is open, I would like to select the appropriate item within the list (if there is a corresponding item).
The twist: The string in the text field is not necessarily equal to the corresponding item in the list. (The strings within the list may e.g. contain additional explanations.)
I tried implementing textDidChange:
- (void)textDidChange:(NSNotification *)notification {
NSString* nsstring = [self stringValue];
[self selectItemAtIndex: [self findCorrespondingIndex: nsstring]]; // changes text - even if documentation states that it "does not alter the contents of the combo box’s text field"
}
But, contrary to the documentation, selectItemAtIndex changes the text in the text field, so I tried the following:
- (void)textDidChange:(NSNotification *)notification {
NSString* nsstring = [self stringValue];
[self selectItemAtIndex: [self findCorrespondingIndex: nsstring]]; // changes text - even if documentation states that it "does not alter the contents of the combo box’s text field"
[self setStringValue:nsstring]; // clears selection
}
But, contrary to the documentation, setStringValue clears the selection.
Is this behavior expected?

NSTextField Autocomplete / Suggestions

Since some days I am trying to code an autocompletion for a NSTextField. The autocompletion should look that way, that when the user clicks on the NSTextfield a list should be displayed under the TextField which possibilities are available. After typing one letter or number the list should refresh with the possibilities.
The suggestions in this list should come from an NSMutableArrayor a NSMutableDictionary
This autocomplete / autosuggestion method should be for a MAC application.
Just adding to #AbcdEfg's answer, to make NSComboBox case-insensitive, you can subclass it and override it's [completedString:] method like this:
- (NSString *) completedString:(NSString *)string {
NSUInteger l = [string length];
if (!!l)
for (NSString *match in [self objectValues])
if ([[match commonPrefixWithString:string options:NSCaseInsensitiveSearch] length] == l)
return [match stringByReplacingCharactersInRange:NSMakeRange(0, l) withString:string];
return nil;
}
You can use NSComboBox for that matter. You also need to set its Autocompletes attribute in IB or [comboBox setCompletes:YES] in code.
Keep in mind that it is case-sensitive.
However if you need it to be done in the exact way that you described, you need to make the list by subclassing NSWindowController and NSTableView and change them to look like a list or menu to show under you NSTextField. Set the NSTextField's delegate and do the search and list update on text changes.
Avoid the NSMenu in this case as it will take away the focus from the text field while you are typing.
Apple addressed it in WWDC 2010 Session 145.
They explained about a text field with suggestions menu and how to get it to work. They also provide the sample codes in their website.
You can find the sample code here.

Writing an app (for my class) which states user input on state names and that particular state's capital.

The app is supposed to work like this:
The user inputs, in a text field, a particular state. In another text field, the user inputs a capital for that state.
After the user hits the "add" button, the key/value pair is sent into both dictionaries, one for pairing State to Capital, and another for comparing Capital to State.
So here's my problem:
All that being said, and the app programmatically creating a button in a scrollView, the State or whichever data was typed into the first field, is represented.
That particular button is supposed to, upon clicking, switch the name of that button to the paired Capital or State, in whichever case. I cannot, for the life of me, find out how to code this into my app.
My Header File:
IBOutlet UIScrollView *scrollView; // for scrollable State / Capital view
IBOutlet UITextField *stateField; // text field for entering state
IBOutlet UITextField *capitalField; // text field for entering capital
// stores the website and passwds
NSMutableDictionary *stateCapital;
NSMutableDictionary *capitalState;
// stores the Buttons representing the passwds
NSMutableArray *buttons;
// stores the info buttons for editing existing passwd
NSMutableArray *label;
// location of the file in which capital are stored
NSString *filePath;
My Implementation:
To add a state
// make the keyboard disappear
[stateField resignFirstResponder];
[capitalField resignFirstResponder];
NSString *key = stateField.text; // get the text in tagField
NSString *value = capitalField.text; // get the text in queryField
// test if either field is empty
if (value.length == 0 || key.length == 0)
return; // exit from the method
if ([stateCapital valueForKey:key] == nil) // test if the website already exists
[self addNewButtonWithTitle:key]; // if not, add a new button
[stateCapital setValue:value forKey:key]; // add a new entry in tags
stateField.text = nil; // clear tagField of text
capitalField.text = nil; // clear queryField of text
[stateCapital writeToFile:filePath atomically:NO]; //save the data
To add a Capital (just reverse the process)
// make the keyboard disappear
[stateField resignFirstResponder];
[capitalField resignFirstResponder];
NSString *key = capitalField.text; // get the text in tagField
NSString *value = stateField.text; // get the text in queryField
// test if either field is empty
if (value.length == 0 || key.length == 0)
return; // exit from the method
if ([capitalState valueForKey:key] == nil) // test if the website already exists
[self addNewButtonWithTitle:key]; // if not, add a new button
[capitalState setValue:value forKey:key]; // add a new entry in tags
stateField.text = nil; // clear tagField of text
capitalField.text = nil; // clear queryField of text
[capitalState writeToFile:filePath atomically:NO]; //save the data
I'm pretty new to programming, and am taking a Summer course, it's extremely compact.
My professor is in and out of the lab, but offers only hints on how to achieve the success of this application. I'm trying to find out via the web and my book on how to do this, but have no success as of yet.
Generally, you want individual IBOutlets for each UI widget that's part of your interaction. (I assume you're using Storyboard…) You have outlets for UITextFields, but your buttons are in an NSMutableArray. This is almost surely not what you want. You also don't have any methods written that handle the extraction of the data from the text fields into the dictionaries you're keeping. So, you need to wire a button to an IBAction method. This method will extract the text from the UITextField IBOutlets (self.stateField.text) and save to the NSMutableDictionary. Be sure your IBOutlets are connected properly to your view controller code and that the buttons are properly wired to your IBAction method. Those keywords should get you on the right track to solving the question.
Related: I see a lot of info about "websites" and "passwds" in your code. This leads me to think you've copy-pasted some other code and are trying to shoehorn your assignment into this code or else this used to be a different assignment and your instructor gave you a starting point that was poorly modified.
Bonus free editorial — It's not 100% clear if the instruction to keep two dictionaries with the same data in reverse order is inherent to the assignment, it's overkill IMHO since you have the allKeysForObject: method to get the key that corresponds to the object being searched. So, NSDictionary *capitalsDictionary = #{#"Texas": #"Austin"} can give you Austin for the key Texas with capitalsDictionary[#"Texas"] and can give you Texas for the value Austin with [capitalsDictionary allKeysForObject:#"Austin"][0] (The [0] is because allKeys… returns an array. If you check this before inserting a new capital, you can be sure that there will only be one. More than one would be a mistake unless somehow more than one state's capital city would be the same name, which is not the case in the USA, but might be elsewhere. By using allKeysForObject: you'd be able to handle this situation but with the multiple dictionaries, you wouldn't because a key can correspond to only one value. You could make that value an array, but it would still have the same problem as allKeys…

How can I manually enable or disable the Return Key on the keyboard?

I have a UITextView that receives input from the keyboard. The "Auto Enable Return Key" checkbox is checked, so when the UITextView is empty the Return key is disabled.
I have an Emoticons bar above the keyboard that can add emoticons to the UITextView as well, but here's the problem; When the UITextView is empty and I add an emoticon to the UITextView like this:
[inputTextView insertText:#"\ue40d"];
then the ReturnKey is still disabled, although the UITextView's text property is not empty.
I've tried this after inserting an emoticon:
[inputTextView setEnablesReturnKeyAutomatically:NO];
With no results. It looks like enabling/disabling the Return Key of the keyboard is only triggered by entering characters through the keyboard.
Any idea's how to do manually enable/disable the Return key?
It's a bit of a hack, but you could try inserting the text via the pasteboard instead:
UIPasteboard* generalPasteboard = [UIPasteboard generalPasteboard];
// Save a copy of the system pasteboard's items
// so we can restore them later.
NSArray* items = [generalPasteboard.items copy];
// Set the contents of the system pasteboard
// to the text we wish to insert.
generalPasteboard.string = text;
// Tell this responder to paste the contents of the
// system pasteboard at the current cursor location.
[textfield paste: nil];
// Restore the system pasteboard to its original items.
generalPasteboard.items = items;
// Free the items array we copied earlier.
[items release];
You can use
textField.setValue(isEnabled, forKeyPath: "inputDelegate.returnKeyEnabled")
Fox example inside
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
uncheck "Auto Enable Return Key"

When is a UITextField's text property set?

I've been having an interesting problem. I've got both a UIButton and a UITextField, lets call them myButton and myField respectively. Now, when the user presses myButton, it is supposed to save the contents of [myField text] to another variable that we'll call otherVar. Now when I type into myField and hit the return key, everything works fine, however when I press myButton before myField has been left, the string that gets stored in otherVar always looks something like:
<UIButtonContent: 0x4e13c60 Title = (null), Image = <UIImage: 0x4b50980>, Background = (null), TitleColor = UIDeviceWhiteColorSpace 1 1, ShadowColor = UIDeviceWhiteColorSpace 0 0.5>
Does the [myField text] not get set until the user presses the return key, leaving the text field? And also, is there a way to tell if the user has left myField before pressing myButton?
Here is the IBAction code for myButton:
-(IBAction)myButton:(id)sender{
if (![[myField text] isEqualToString:#""]) {
[[[appDelegate moduleList] objectAtIndex:[appDelegate moduleNum]] setOtherVar:[myField text]];
}
}
thanks!
Try adding [myField resignFirstResponder]; before your if statement in the IBAction method. This should commit any of the text edits that have been made, and populate the text property.
You can use notifications to know exactly when a UITextField has changed its value, discussed in the docs here.
More about notifications here.
There are also delegate methods for UITextFields which let you know when editing begins and ends, among other things - docs here.