UITextField Autocomplete? - ios

I am trying to figure out if there is a way to implement an autocomplete functionality in a UITextField for specific values.
I know that the UITextField can do this using the iPhone dictionary (much like searching google in safari, etc), but I want to be able to programmatically have it correct to certain values that I specify.
How to do this?

I did something very similar to this while working on a recent and rather large project. We had a constantly changing list of auto complete terms and built an auto-complete around them.
First, you'll want to make some type of auto-complete controller. It should take a string and return all possible auto complete terms for that string.
-(NSArray *)completionsForString:(NSString *)myString;
Then, check out the UIMenuController class. It's the class that shows the cut/copy/paste options in many applications. You can get the shared instance of it, populate the menu items yourself, and show it above the text field. The user can then simply tap the term they want.
In the end, the solution worked really well for our needs.

Alternatively, you can use this UITextField subclass (inspired by DOAutocompleteTextField):
https://github.com/hoteltonight/HTAutocompleteTextField
It's got a few more features and is actively developed. The example shows you how to use an array as the data source for the autosuggest text. It takes the same approach as DOAutocompleteTextField, in that it shows the suggested completion text "ghosted" in the text field as the user types.

Have you looked into UISearchDisplayController? There are a few threads here on Stack Overflow, including Core Data references if that is what you are using. Also some alternative methods, elsewhere.

With the help of the aforementioned Ray Wenderlich tutorial, I just implemented a version of this to filter names in an existing UITableView.
I set my text field's delegate as my view controller, my view controller as a UITextFieldDelegate and implemented these two methods:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *substring = [NSString stringWithString:textField.text];
substring = [substring stringByReplacingCharactersInRange:range withString:string];
[self searchAutocompleteEntriesWithSubstring:substring];
return YES;
}
- (void)searchAutocompleteEntriesWithSubstring:(NSString *)substring
{
NSMutableArray *autoCompleteArray = [[NSMutableArray alloc]init];
[self retrieveData];
for(NSString *curString in _staffTableArray)
{
NSString *lowerCaseCur = [curString lowercaseString];
NSRange substringRange = [lowerCaseCur rangeOfString:substring];
if (substringRange.location == 0)
{
[autoCompleteArray addObject:curString];
}
}
if (![substring isEqualToString:#""])
{
_staffTableArray = [NSMutableArray arrayWithArray:autoCompleteArray];
}
[_staffListTableView reloadData];
}

use this delegate method. you can replace values that you specify.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; // return NO to not change text
if ([string isEqualToString:#"StackO"]) {
textField.text=#"StackOverflow";
}
return YES;
}

Just faced with this thread because I need something similar. How about implementing you own search with the UITextfieldDelegate's method:
- (BOOL)textField:(UITextField *) textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
As you'd probably know this method is called for every UITextfield's typing.

Related

Unit Testing UITextField Method

I'm very much new to iPhone app development, so starting with some unit testing. Pardon me if it is trivial question.
I have following code for a text field,
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
//Check whether currently editing text field is a account name text field
if([[_customCellObjects objectAtIndex:0] textField] == textField) {
NSUInteger newLength = [[[_customCellObjects objectAtIndex:0] textField].text length] + [string length] - range.length;
//Return NO(Non Editable) if the account name text field exceeds more than 50 characters
return (newLength > 50) ? NO : YES;
}
else {
//Return YES(Editable) for the other text fields
return YES;
}
}
How to invoke the above method with XCTest? and how to pass parameters to it?
What I do for UITableViews is expose the table view as a property, then call the delegate/data source functions directly and check the desired output.
In your case you would create a textField property that returns [[_customCellObjects objectAtIndex:0] textField] Or expose _customCellObjects
YourViewController *vc = [[YourViewController alloc] init];
bool result = [vc textField:vc.textField shouldChageCharactersInRange:NSMakeRange(0,1) replacementString: "a"]l
XCTAssertTrue(result);
The only thing I don't like about this is exposing the UITextField (or UITableView) unnecessarily. But others may post better options.

How can I recognize keyboard presses in ios?

I have looked all over for an answer to this but essentially what I am trying to do is when a person pressed the colon key on their iphones keyboard I want to be notified and perform a certain action. I hope this makes sense. If you do offer an answer keep in mind I am a relatively new IOS developer :)
Thanks!
edit: Incase my above statement didn't quite make sense this is what will happen ideally:
user taps on textfield
user presses the number 1 key
notification is sent that user pressed the number 1 key
instead of the number 1 printed, the text will be replaced with the number 2.
this is a simple example.
Here's an example of a delegate method for a UITextField where if the user tries to enter an uppercase character it will appear as a lowercase character instead:
-(BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
NSString* lc = [string lowercaseString];
if ([string isEqualToString:lc])
return YES;
textField.text =
[textField.text stringByReplacingCharactersInRange:range
withString:lc];
return NO;
}
You should be able to do something similar for your particular use case.
As mentioned before, use this callback and change in there:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//check here if the new character is the one you are looking for
if ([string isEqualToString:#"a"])
{
//create a new string with the character you want to use instead
NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:#"A"];
//set it as the text for your text field
[textField setText:newText];
return NO;
}
return YES;
}

Two TextFields in a View Controller set as the delegate, causing the app to crash with NSRange, range or index out of bounds

I have a view controller that annoyingly keeps crashing. The textfield has 3 textFields (name, item, title) and everything was working well before I tried to introduce the UITextFieldDelegate method.
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
When I did that and in viewDidLoad, set the nameTextField as the delegate, as well as itemTextField and titleTextField, when I ran the app and typed a few characters into the itemText/titleTextField, it would crash the program with the following error:
Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFString replaceCharactersInRange:withString:]: Range or index out of bounds'
If I simply remove the self.itemTextField.delegate = self, it does not crash the app when I type into that field and the same goes with the titleTextField.
To emphasise this point though, the nameTextField set as the delegate continues to work without crashing. However it appears that adding another textField as the delegate also causes the crashes here.
I have the following two methods which aid in me providing a autocomplete table view under the nameTextField so when users type in, they can reference previously typed in names (stored in Core Data, etc).
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
self.autoCompleteTableView.hidden = NO;
self.substring = [NSString stringWithString:self.nameTextField.text];
self.substring = [self.substring stringByReplacingCharactersInRange:range withString:string];
NSLog(#"Value of entered string = %#", self.substring);
[self searchAutocompleteEntriesWithSubstring:self.substring];
return YES;
}
Which calls:
- (void)searchAutocompleteEntriesWithSubstring:(NSString *)substring
{
self.autocompleteFetchedResultsController = nil;
[self autocompleteFetchedResultsController];
NSLog(#"Table = %#", self.autoCompleteTableView);
[self.autoCompleteTableView reloadData];
}
I know there are a ton of questions and blog posts relating to this error, but I'm not really sure how to solve this.
I noticed the crash when trying to implement the UITextFieldDelegate method mentioned above and I'm seeing that self.titleTextField.delegate = self in the viewDidLoad is enough to crash this. self.nameTextField.delegate = self is set and that one works regardless of the titleTextField and itemTextField.
Any shedding of light on this would be appreciated!
Thanks,
The issue is happening in this method below, You have 3 text fields and all of them call this method but you assume this is happened just for self.nameTextField:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
self.autoCompleteTableView.hidden = NO;
self.substring = [NSString stringWithString:self.nameTextField.text];
self.substring = [self.substring stringByReplacingCharactersInRange:range withString:string];
NSLog(#"Value of entered string = %#", self.substring);
[self searchAutocompleteEntriesWithSubstring:self.substring];
return YES;
}
You can fix it in many ways, it depends what is required for you, one of them is compare that edited text field is your self.nameTextField, but I don't know is it whay you want to achieve:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (self.nameTextField == textField)
{
self.autoCompleteTableView.hidden = NO;
self.substring = [NSString stringWithString:self.nameTextField.text];
self.substring = [self.substring stringByReplacingCharactersInRange:range withString:string];
NSLog(#"Value of entered string = %#", self.substring);
[self searchAutocompleteEntriesWithSubstring:self.substring];
}
return YES;
}
Move your NSLog statement in shouldChangeCharactersInRange: to the very first line of that function and you will get an idea what's happening.
Hint: your delegates get called from different TextFields...
Change self.nameTextField to textField inside - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range.
You should use the textField that called the delegate, not a class member that could be different that the textField being modified by the user

UITextFieldDelegate, checking if textFields have text in it

i have a little problem.
There are two textFields in my TableView which set the property of an object. In order to do so i want to force the user to write something in the textField before the string is actually been set to the object. So basically a simple ([textField.text length] > 0) thing.
But i want that the user have to write strings in both the two textFields to finally enable the "Done"-Button.
I solved this earlier but with only one text Field with the following UITextFieldDelegate method.
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newText = [theTextField.text stringByReplacingCharactersInRange:range withString:string];
self.doneBarButton.enabled = ([newText length] > 0);
return YES;
}
My solution for the new problem, so now with two textFields is this one:
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newText = [theTextField.text stringByReplacingCharactersInRange:range withString:string];
if ([theTextField.placeholder isEqualToString:#"textField1"]) {
if ([theTextField.text length] > 0) {
enabledVokabel = YES;
} else {
enabledVokabel = NO;
}
}
if ([theTextField.placeholder isEqualToString:#"textField2"]) {
if ([theTextField.text length] > 0) {
enabledUebersetung = YES;
} else {
enabledUebersetung = NO;
}
}
self.doneBarButton.enabled = (enabledVokabel && enabledUebersetung);
return YES;
}
So i want the doneBarButton been enabled when both of the textFields (textField1 and textField2) are filled with text. But i want it that way that if the user has deleted the text he/she just wrote in the doneBarButton is disabled as soon as the textFields are empty.
It doesn't work that way. Do you have a solution? Or maybe a better way to solve it?
Either just connect value changed in interfacebuilder to a IBAction method in any of the classes you have in your view. Or you can do it in code with:
[textField addTarget:self
action:#selector(myIBActionMethod:)
forControlEvents:UIControlEventEditingChanged];
And check the length of the input.
You can of hook up both textfields to the same method and check the length of both textfields every time its called if you have IBOutlets to them both.
I'd keep a reference for both UITextViews, lets say.-
IBOutlet UITextView *textView1;
IBOutlet UITextView *textView2;
properly linked to your xib/storyboards. Also, I'd rather use
- (void)textViewDidChange:(UITextView *)textView
callback. According to shouldChangeCharactersInRange docs, it looks like this method is called before actually changing the text.
As for the enabled condition, it would look something like this.-
self.doneBarButton.enabled = [textView1.text length] > 0 && [textView2.text length] > 0;

clearing a UITextField when the user starts typing

short version: How can I make a UITextField box remove all content on the users first keypress? I don't want the info removed until the user starts typing something. ie, clearing it on begin edit is not good enough.
long version: I have three UITextField that loop around (using the return key and catching the press in the "shouldReturn" method. There is text already in the UITextField, and if the user doesn't type anything and just goes to the next UITextField, the value should stay (default behaviour).
But I want it that if the user starts typing, it automatically clears the text first. Something like having the whole field highlighted, and then typing anything deletes the fiels and then adds the user keypress.
"Clear when editing begins" is no good, because the text is immediately cleared on the cursor appearing in the field. That's not desired. I thought I could use the placeholder here, but that doesn't work as a default, and I can't find a default value property. The Highlighted and Selected properties don't do anything in this regard either.
There is a delegate method called
textFieldDidBeginEditing:(UITextField*) tf{
tf.startedEdinting = YES;
}
textFeildDidEndEditing: (UITextField*) tf {
tf.startedEditing = NO;
}
Add startEditing in a category to UITextField.
Then if value changes clear the field:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField.startEditing){
textField.text = string;
} else {
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
}
}
You can add the property to the UITextField category in the following way:
.h
#property (nonatomic, assign) BOOL startEditing;
.m
#dynamic startEditing;
- (void) setStartEditing:(BOOL)startEditing_in{
NSNumber* num = [NSNumber numberWithBool:startEditing_in];
objc_setAssociatedObject(self, myConstant, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL) startEditing{
NSNumber* num = objc_getAssociatedObject(self, myConstant);
return [num boolValue];
}
Declare a BOOL variable in your .h file like.
BOOL clearField;
And implement the delegate methods like:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
clearField = YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
clearField = NO;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
clearField = NO;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(clearField)
{
textField.text = #""
clearField = NO;
}
}
I want to thank people for their answers, I implemented both of the main methods described here and both worked flawlessly. But I have since come across a much simpler, nicer answer and involves only one line of code :)
In the textField's didBeginEditing method, place [self.textField selectAll:self]; or [self.textField selectAll:nil];
The original answer I found had selectAll:self but this shows the cut/copy/paste menu. If you send nil instead of self the menu doesn't appear.
Adding this one line of code highlights the text on entering the textField (so gives the user a visual cue), and only removes everything once a key is pressed.
Another solution that fulfils the same purpose is by simply using a text field placeholder which is defined as:
The string that is displayed when there is no other text in the text field.
So as soon as the user starts typing, the placeholder text disappears.
That's something you can set from the storyboard, or programmatically. (Yes it took me two hours trying to figure it the harder way.. when the solution was literally one line change of code).
If you want to clear the text one the user interacts with it, there is an option in interface builder to where you can set the text field to "Clear when editing begins."
Try to use the following method.
- (BOOL) textField: (UITextField *)theTextField shouldChangeCharactersInRange: (NSRange)range replacementString: (NSString *)string {
if(isFirsttime==YES)
{
textfield.text==#"";
isFirsttime=NO;
}
return YES;
}
Declare and initialize a NSString variable for your textField's initial text
NSString *initialText=#"initial text";
Then implement methods:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
if(textField.text isEqualToString:initialText)
{
textField.text=#"";
}
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
if(textField.text isEqualToString:#"")
{
textField.text=initialText;
}
}

Resources