Special auto-completion behaviour for NSComboBox - objective-c

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?

Related

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.

shouldChangeCharactersInRange doesn't get the last number

I have this code that should catch exactly what the user types:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSLog(#"Catch -> %#",textfield.text);
}
But When the user digit the letter A, the console return me ('null'), When the user digit the letter AB, the console return me only the letter A, in this case the method shouldChangeCharactersInRange doesn't return the last letter that user types.
To change this I try to change my code to:
NSLog(#"Catch -> %#",string);
In this case, the console shows me only the last number. How can I solve this problem and pick the complete sentence that user digit?
This method is called BEFORE the text field has changed. If you speak English, you would have already known this just by reading the method name, one of the big advantages of Cocoa...
This method gives you a chance to say "NO, don't change the text in this text box!" or to change the text in the box before the change is made, or to change the text that will be added before it's added.
textfield.text won't reflect the change until after this method returns.
To catch the END of editing events for a textfield, put this in your viewDidLoad:
[yourTextField addTarget:self
action:#selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
Where yourTextField is a reference to the text field of interest.
Now write the method:
- (void)textFieldDidChange:(UITextfield *)textfield {
NSLog(#"Catch -> %#", textfield.text);
}
This delegate method for me usually starts out like this:
- (BOOL)textField:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSString *candidateString = [textView.text stringByReplacingCharactersInRange:range withString:text];
Now, candidateString will contain what you expect. It's done this way so that any manner of input (even a paste into the middle of the text) can be understood before and after the text field's text is updated

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…

Want to make UITextView react to return key

I'm trying to implement a program in Xcode that's somewhat like a command line. I basically have a UITextView that can take multiple lines of text. Right now, I have a button that will make the necessary changes after the user is done entering commands, but I want to be able to have a method that gets called after the user hits the return key in the UITextView, so basically it makes changes after each command. Is it possible to do this?
The BOOL method mentioned above is a wrong answer... for one the person is checking the text from the TextView the moment before it is updated so they are viewing the old text... Also the methods are out of date.
This usage will work immediately once the return key is pressed (The current "answer" will not work until after the return key has been pressed and then ANOTHER key is pressed):
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
NSLog(#"Return pressed");
} else {
NSLog(#"Other pressed");
}
return YES;
}
Don't forget to add the UITextViewDelegate to your .h file's protocols.
#interface ViewController : UIViewController <UITextViewDelegate> {
and set yourTextView.delegate = self; in .m file!
/*
note: This will also get called if a user copy-pastes just a line-break...
unlikely but possible. If you need to ignore pasted line-breaks for some
reason see here: http://stackoverflow.com/a/15933860/2057171 ...
Now for an unrelated-tip: If you want to accept pasted line breaks however
I suggest you add an "or" to the conditional statement and make it also
check if "text" isEqualToString #"\r" which is another form of line-break
not found on the iOS keyboard but it can, however, be found on a website
and copy-pasted into your textView. If you want to accept pasted text
with a line-break at the end you will need to change the
"isEqualToString" code above to say "hasSuffix", this will check for
any string %# with a "\n" at the end. (and again for "\r") but make
sure you don't call your "next" method until after `return YES;`
has been called and the text view has been updated, otherwise
you will get only the text that was there before the copy paste
since this is "shouldChangeTextInRange" method, not
"didChangeTextInRange", if you do this I suggest stripping the
"\n" or "\r" from the end of your final string after the copy-paste
was made and applied and the text was updated.
*/
If you set a delegate to the UITextView which implements
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
Then you can check if the last character is "\n", and get the text entered since the last command by doing
NSArray* components = [textView.text componentsSeparatedByString:#"\n"];
if ([components count] > 0) {
NSString* commandText = [components lastObject];
// and optionally clear the text view and hide the keyboard...
textView.text = #"";
[textView resignFirstResponder];
}
Note, I haven't tested this, just an idea:
- (void)textViewDidChange:(UITextView *)textView
{
if ([[textView.text substringWithRange:NSMakeRange(textView.text.length - 1, 1)] isEqualToString:#"\n"])
{
[textView resignFirstResponder];
[self methodYouWantToCall];
}
}
You can do this by setting up a delegate for the UITextView. See UITextViewDelegate Protocol Reference for details on what can be done.

NSTextField delegate notifications -- how to get text?

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.