I'm have a weird issue here. I have a UITableView using custom UITableViewCells. Everything is working as expected except this on particular issue.
Here's the scenario:
I need to remove the "$" symbol in a UITextField right before editing begins. This is done via the textFieldShouldBeginEditing: method.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
LifeEarningsLineItemTableViewCell *cell = (LifeEarningsLineItemTableViewCell *)[self tableViewCellContainingObject:textField inTableView:self.lifeEarningsTableView];
if (textField == cell.itemAmount) {
/*Remove currency symbol for editing.*/
NSString *currencySymbol = [self.currencyFormatter currencySymbol];
NSMutableString *mutableText = [NSMutableString stringWithString:textField.text];
[mutableText replaceOccurrencesOfString:currencySymbol withString:#"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [mutableText length])];
textField.text = mutableText;
}
return YES;
}
Here's the problem:
Between different rows, moving from textField1 to textField2 (in a different row), the "$" is removed, this is GOOD.
Within the same row, moving from textField1 to textField2, the "$" is not removed, this is BAD.
Why is the "$" not being removed within the same row, but does in different rows?
Here's is a visual representation of the issue:
[EDIT...ADDITION]
I get these logs with these flows:
SHOULD BEGIN.....Row:0, Tag:0
SHOULD BEGIN.....Row:0, Tag:1
DID END.....Row:0, Tag:0
DID END.....Row:0, Tag:1
SHOULD BEGIN.....Row:0, Tag:0
SHOULD BEGIN.....Row:1, Tag:1
DID END.....Row:0, Tag:0
DID END.....Row:1, Tag:1
Also, here is the tableViewCellContainingObject:inTableView:tableView method:
- (UITableViewCell *)tableViewCellContainingObject:(UIView *)view inTableView:(UITableView *)tableView {
CGPoint objectRectInTableViewCoordinates = [tableView convertPoint:view.bounds.origin fromView:view];
NSIndexPath *cellIndexPath = [tableView indexPathForRowAtPoint:objectRectInTableViewCoordinates];
return [tableView cellForRowAtIndexPath:cellIndexPath];
}
And the textFieldDidEndEditing method:
- (void)textFieldDidEndEditing:(UITextField *)textField {
LifeEarningsLineItemTableViewCell *cell = (LifeEarningsLineItemTableViewCell *)[self tableViewCellContainingObject:textField inTableView:self.lifeEarningsTableView];
LifeEarningsLineItem *lifeEarningsLineItem = [self.lifeEarningsFetchedResultsController objectAtIndexPath:[self.lifeEarningsTableView indexPathForCell:cell]];
if (textField == cell.itemAmount) {
NSNumber *absInteger = [NSNumber numberWithInteger:abs([textField.text integerValue])];
textField.text = [self.currencyFormatter stringFromNumber:absInteger];
lifeEarningsLineItem.amount = absInteger;
[self sumAmountsAndDisplay];
} else if (textField == cell.itemName) {
lifeEarningsLineItem.name = textField.text;
}
}
I think your solution (decoupling the FRC during editing) may be a little drastic and could have unforeseen effects. Here are a couple of alternative suggestions. I am assuming the problem is caused by the table reloading the row you are editing once you have finished editing the first field in the cell.
Don't store the currency symbol in your model, add it to the displayed text in cellForRowAtIndexPath, if the text field is not editing. When your textfield begins editing, set its text to the value directly from the model. You don't have to do anything in end editing since the reload will add the currency symbol back on for you.
If you dont want to change the model, you can do something similar anyway - in cellForRowAtIndexPath remove the currency symbol if the cell is editing.
Store the index path of the currently editing row and conditionally ignore changes to this row in the FRC delegate method.
Okay, I figured out the problem...it was the gosh darn NSFetchedResultsController delegate methods, specifically the NSFetchedResultsChangeUpdate change type. This is not the first time where NSFetchedResultsController walked all over my table. I should have remembered that NSFetchedResultsController doesn't play well with user-driven edits.
So here is how I have resolved the issue (but I'm still in fear that the whole thing could all come crashing down):
Because of the order of events...
SHOULD BEGIN.....Row:0, Tag:0
SHOULD BEGIN.....Row:0, Tag:1
DID END.....Row:0, Tag:0
DID END.....Row:0, Tag:1
...I couldn't just use a BOOL property to say a text field was in editing mode (the order of events would have to be BEGIN->END->BEGIN->END).
So I created the property:
#property (nonatomic, strong) NSMutableSet *stackOfEditingTextFields;
Then at the start of textFieldShouldBeginEditing: method, I added:
/*Add text field to stack of editing text fields and disable the fetched results controller delegate.*/
[self.stackOfEditingTextFields addObject:textField];
self.lifeEarningsFetchedResultsController.delegate = nil;
Then at the end of textFieldDidEndEditing: method, I added:
/*Remove text field from stack of editing text fields. If stack count is 0, reengage the fetched results controller delegate.*/
[self.stackOfEditingTextFields removeObject:textField];
if ([self.stackOfEditingTextFields count] == 0) self.lifeEarningsFetchedResultsController.delegate = self;
If there's a better suggestion or any unforeseen bi-product bugs that I'm not seeing, I'm all ears.
Thanks for the help lnafziger, in helping me work through it, and suggesting different angles (I +1'd your comments).
Related
I have a UITableView with custom UITableViewCells and each tableview cell has a UITextField. By default the textfield has a title already in place that can be edited by the user. The default title in the textfield is associated with a file in NSFileManager and when the user finishes editing the text field and taps "return", a method that changes the file name to what the user enters gets called. This works fine, but when the user taps the textfield but doesn't do any editing and then taps "back" to go to the previous view controller, I get a warning from NSFileManager saying the file name already exists. This doesn't cause any problems, but its annoying. I know the method that calls NSFileManager to change the file name shouldn't get called unless the user edits the textfield, but I'm not sure of the best way to implement this.
I saw this post, but wasn't sure how to incorporate it into what I'm doing:
UITextField text change event
I was wondering if someone could give me some tips on how to make this work.
-(void) textFieldDidEndEditing:(UITextField *)textField
{
textField.delegate = self;
NSArray* cells = [self.audioTable visibleCells];
for (OSAudioTableCell* cell in cells)
{
if (textField == cell.textField)
{
NSInteger index = cell.tag;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
Recording * recording = [self.fetchCon objectAtIndexPath:indexPath];
NSString * previousPath = recording.audioURL;
//I left a lot out, but this is where I call the method to change the file name
NSString * returnedURL = [self.managedDocument changeFileName:previousPath withNewComponent:textField.text error:&aError];
}
}
}
I would just check if the textField's text changed. If it did then go through the block you pasted above. If not, then just do nothing. You can do this by holding a temporary reference to your textfield's value before any edits happen:
// At the top of your class
#property (strong, nonatomic) NSString *currentFileName;
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
_currentFileName = textField.text;
}
Then in your method above, I would check if the two strings are not equal:
-(void) textFieldDidEndEditing:(UITextField *)textField
{
if (![textField.text isEqualToString:currentFileName]) {
// continue with your logic
}
}
Try this. Add the delegate method -(void)textFieldDidBeginEditing:(UITextField *)textField. And in this method do something like:
-(void)textFieldDidBeginEditing:(UITextField *)textField {
self.textBeforeEditing = textField.text;
}
And then, do a compare when textFieldDidEndEditing is invoked:
-(void) textFieldDidEndEditing:(UITextField *)textField {
...
if(![self.textBeforeEditing isEqualToString:textField.text]) {
// Change the file name
}
...
}
You could implement textFieldDidBeginEditing:, in which you would store the unedited value of the UITextField in an instance variable. Then, in textFieldDidEndEditing: simple compare the before and after values, and if they're different call your NSFileManager method like you normally would.
Example
#interface MyClass () {
#property (strong, nonatomic) NSString *originalText;
}
#implementation MyClass
- (void)textFieldDidBeginEditing:(UITextField *)textField {
self.originalText = textField.text;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if ([self.originalText isEqualToString:textField.text]) {
// Your original code here.
}
self.originalText = nil;
}
#end
I've a UITableViewController which has Save button. At present I'm displaying row's of UITableViewCell's. UITableViewCell contains textfield . Let's say if user enters text in 1st table cell and hits "Save" then I'm losing the entered/modified text.
suppose I have a textField
((UITextField*)[cell viewWithTag:101]).placeholder = #"title";
((UITextField*)[cell viewWithTag:101]).text = arrayActivities[indexPath.row][#"title"]; //arrayActivities[indexPath.row];
((UITextField*)[cell viewWithTag:101]).keyboardType = UIKeyboardTypeDefault;
how I would save arrayActivites editable values??
Have you tried making the UITableViewController a UITextViewDelegate? So in your tableView's .h file:
UITableViewController <UITextFieldDelegate>
and in your .m file you should be able to call:
- (void)textFieldDidEndEditing:(UITextField *)textField {
[array addObject: textfield.text];
}
Keep one global dictionary dctGlobal in your class.
Set textfield.delegate=self in cellForRowAtIndexPath method.
And then get and save value for text field in following method
- (void)textFieldDidEndEditing:(UITextField *)textField
[dctGlobal setObject:textField.text forKey: textField.placeholder];
On save, Use dctGlobal for saving into database.
Thanks!
I have a row of labels that have been programmatically instantiated, they are stored in an NSMutableArray. They don't currently contain any data. What I'm trying to do is make it so that when a user types in a character it is automatically displayed in the labels. I'm not sure how to do this. I know how to access the labels I have created [MyArray ObjectAtIndex:0] and so on, but how could I make it so that when a user types on the keyboard it formats the text (I have code for formatting) and then just appears on screen.
I need help putting each character on the screen as it is typed.
Any help would be greatly appreciated - I have a textfield (it's hidden) and the keyboard comes up by button. If that helps. :)
Thank you in advance :).
UITextField *tf;
[tf addTarget:self action:#selector(editingChanged:) forControlEvents:UIControlEventEditingChanged];
- (void)editingChanged:(UITextField *)textField {
_myHiddenLabel.text = textField.text;
}
You can add observer when Text inside UITextField changes and then access your labels and add text to it...
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(changeLabelsMethod:)
name:UITextFieldTextDidChangeNotification
object:myHiddenTextField];
}
-(void)changeLabelsMethod:(UITextField*)txtField
{
Static int i=0;
if(i<[MyArray count])
{
UILabel *lbl=[MyArray ObjectAtIndex:i];
lbl.text=txtField.text;
}
else
return
i++;
}
EDIT: Refer Eugene's answer for right approach
You can get notified every time a character is typed if you set your viewController as delegate to your hidden textField and implementing UITextFieldDelegate Protocol
- (IBAction)textFieldValueChanged {
NSString *strLastChar = [txtSearch.text substringFromIndex:txtSearch.text.length-1];
UILabel *lblCurrent = [arrSearch objectAtIndex:intCurrentLblNo];
[lblCurrent setText:strLastChar];
intCurrentLblNo++;
}
take intCurrentLblNo as global variable and set intCurrentLblNo = 0; in viewdidload method
and set it...
[txtSearch addTarget:self action:#selector(textFieldValueChanged) forControlEvents:UIControlEventValueChanged];
in viewdidload method
I know this is a basic question but i´m a little confused, so i hope you can help me. I have a tableview with multiple dynamic tableview cells, and inside each tableviewcell i have multiple textfields. Each cell has a different tag and also the textfields and i want to access the uitextfields values as you can imagine. My problem is, i´m not using IBoutlet for the textfields (it would be a enormous amount of IBoutlets)...I´m using - (void)textFieldDidEndEditing:(UITextField *)textField...but i just can´t seem to make the correct connections in the IB, this is my code:
-(void) textFieldDidEndEditing:(UITextField *)textField
{
if (textField == [self.view viewWithTag:102]) {
[textField resignFirstResponder];
}
After this, do i have to connect the respective UItexfield (and all textfields) to self? and then, do i have to use the editing did end event?...
Regards
I guess that the answer to this question is another question: What do you want to do with the text that the user enters?
I assume that you have some kind of data model that you want to store the data in.
If so, then when this function is called, you need to take the text that is already in the textField and save it to your data model immediately as it is entered.
For instance, you can access the text that was entered like this:
-(void) textFieldDidEndEditing:(UITextField *)textField
{
if (textField == [self.view viewWithTag:102])
{
[textField resignFirstResponder];
yourDataModel.stringToSave = textField.text;
}
}
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.