Odd behavior iterating through UITableViewCells - ios

I have user input a form based on a UITableView where each cell contains an input control, either a UITextField or a UITextView. I've added Next and Previous buttons to the accessory views of the text fields so that users can navigate forward and backward through the text fields. I have a method that gets the control of the text fields and makes it first responder. Everything so far works properly.
In my method that sets the first responder, I'm calling a helper method that gets the next cell, going either forward or backward:
- (UITableViewCell*)nextFormCell:(UITableViewCell*)lastCell isForward:(BOOL)forward
{
NSIndexPath* path = [_formTableView indexPathForCell:lastCell];
NSInteger rowIndex = forward ? (path.row + 1) : (path.row - 1);
UITableViewCell* nextCell = [_formTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:rowIndex inSection:path.section]];
return nextCell;
}
Going forward (next) works flawlessly and this method always returns a cell. However, going backward (previous), the method sometimes returns nil. I have verified that rowIndex contains the correct value going both directions. This doesn't make great sense. If I pass a rowIndex going forward and I get a cell back, why would it not do the same with the same index going backward?
Are there any known issues with navigating backward through UITableView cells? Thanks!

You are forgetting that cells are reused. No cells exist at any given moment except for those displayed on the screen and possibly one or two further forward. cellForRowAtIndexPath: does not work for every index path, because not all cells exist at any one moment: only the cells actually displayed by the table exist. That is why cellForRowAtIndexPath: is permitted to return nil.
Basically you need to rethink your entire strategy for "navigating" the table. Instead, start by scrolling to the cell you want to navigate to (scrollToRowAtIndexPath:atScrollPosition:animated:). Now the cell exists and you can set its text field as first responder.

Related

Does cellForRowAtIndexPath get called multiple times for the same "item"?

Let's say I have a list of items that represent my data source:
self.items = [User]()
for model in self.fetchedItems(20){
self.items.append(model)
}
self.tableView.reloadData()
Let's say that my screen only fits 5 items.
Let's say that the first item in the array (indexPath = 0) is the user Hannah.
If the user scrolls all the way down to the bottom, then all the way back up, will cellForRowAtIndexPath get called for Hannah?
It might, it might not. There is no guarantee one way or the other. One possibility is that the cell that held the Hannah object will be returned by the dequeue method for a later index path and then when you scroll back up, the cellForRowAtIndexPath will get called gain for cell 0, and maybe it gets passed the same cell that was passed to cell 5... i.e., it is quite possible that the same cell will be used for multiple rows.
That's why it is so important to set the entire state of the cell when you dequeue it. Possibly using prepareForReuse as a helper. It is also why it's important to keep the array state fixed unless you explicitly tell the table view you have changed it (by calling reloadData or begin/end updates.)

How to update a UITableView so that UITextField in a cell won't lose focus

The setup: The table has 1 custom cell. This cell has 1 text box. In each text box a number is entered. The tableView has 5 rows (of this cell). If I change one number in any of the cells, then all the cells need to be updated, I simply call tableView.reloadData(). The change in the text box is handled with the editingDidEnd event.
The problem: I click any of the textFields and change the number. Then, I click another textField to change its value. first editingDidEnd is called, all the values are re-calculated, and tableView.reloadData is called. Now, the click to the second textField is missed because the table is reloaded.
I am not able to update the cells directly because they are custom cells and the value is changed within that class. I cannot update ALL the cells from custom cell class.
A difficult question to explain in writing especially for a non native English speaker. If you understand the question, any pointers will be appreciated :).
Using Xcode 7.1 and Swift. Thank you.
When you call reloadData, all the cells are removed from the tableview and re-added. Since it is removed, it is no longer in the responder chain meaning its text field can't be the firstResponder (selected).
There are two options that could work.
Keep track of the row that is selected, call reloadData then call becomeFirstResponder on the text field for the correct row.
Not call reload data and just update the values in the text fields. This option more depends on the striation of your app.
This can be quite simple, ideally you are in a position to store the values for the field you want to focus and the index path of the cell in question (such as when you regenerate the editing field after your reload?)
This code should help:
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath == self.editingIndexPath)
{
[self.editingTextField becomeFirstResponder];
}
}

Update UITableViewCell based on alert view button pressed

I have a tableview with custom cells. Each cell contains a button you press, that prompts an alert.
The alert uses the UIAlertView delegate as expected, and it checks the title of the button to determine what the user pressed. This in turn calls a web service that submits data. Once that submission completes, I need to have the button in that cell change to another button image.
Reloading the data so that it reflects the updates sent to the server and then reloading the tableview is having no effect.
Is there a way to access the cell outside of the cellForRowAtIndexPath? I have it setting a global variable named selectedIndex based on what button I press, but that is as far as I can get. Is it even plausible (or a good idea) to access a cell this way.
Thanks for any help!
There are quite a few ways to reload the data in a tableview. But for your case, if you can get a reference to the cell, it would suffice to do little UI related changes(changing images or something like that).
If you think your table's content size is going to vary after the web service returns response, then you must reload the table. Ofcourse you can reload the whole table's content, or just a few rows or sections. Check out the official documentations for the detailed information on them
The global variable is not a good way to get the index of the cell, of which button is pressed. A better approach would be if you use the tag property of the button. Set the tag of the button with the row(or section whatever your schema is) of indexPath. Then you can fetch the tag in your button's selector method.
Get a reference to the cell when the web service call finishes, get an instance of NSIndexPath via indexPathForRow:inSection:.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag inSection:0];
Check if the cell is still visible and go forward with that.
MyCustomCell *cell = (MyCustomCell *)[self.tblView cellForRowAtIndexPath:indexPath];
and do your changes on that cell there.
UITableView has a method (not the delegate method) called -cellForRowAtIndexPath:. Provided that cell is still visible, and you have stored the index path off, you can use that to obtain the cell. It will return nil if the cell is not visible (but in that case the delegate method will be called later on to provide a cell for that index path again before it becomes visible, so you would configure it then).
The caveats are that a web services call is presumably asynchronous, and lots of things can change between when the user presses the button and you get the web response. The user could have scrolled the cell offscreen such that it is no longer visible, in which case you would need to store off the updated value somehow so you can properly configure the cell later on. Also, if your code calls reloadData for any reason, even keeping the NSIndexPath may not make any sense -- if your underlying model has changed at all, the model record associated with the user's click may now be at a completely different NSIndexPath (or may not exist in the data set at all). Depending on your situation those may or may not be issues, but if they are, it may be best to keep a reference to the selected record (or the primary key of that record), and when the call comes back, determine what the index path to that record now is, and see if the cell for that index path is visible.
Lastly, I'd recommend using properties of your view controller to store off these values, not globals. If you ever create a new instance of the view controller, and you change behavior based on the value of the global variable, it may still be set (with completely bogus values) from the previous usage of that view controller class.

Best approach for editing text fields in a table view cell

In my app I've got a number of views that need to be in-place editable. I've got the tableviewcells setup to include a UITextField which is enabled, and the text can be changed and this is fine, it works.
My question is what is the best approach to keeping track of these? Should I :
Keep an iVar of each textfield. If so then
How do I make sure each cell has the correct field if it's only instantiated once?
Have a textfield added when the cell is created and simply set its value when configuring the cell. If so then
How do I keep track of the values? If the cell went out of view and then back, its values would be reset to its original value and not the edited value.
What approach do you generally tend to use for this scenario? Especially if there are a large number of cells that you're dealing with.
Store the values for all of your text fields in a mutable array in your data model.
In tableView:willDisplayCell:forRowAtIndexPath: set your text field's value, using the value from your array based on the index path.
When a text field ends editing, store the value back in the array immediately. There is already a protocol for this (so no need to write your own), but it is a little trickier than normal because you are not handed the indexPath and need to find the one associated with your text field:
- (void) textFieldDidEndEditing:(UITextField *)textField {
CGRect location = [self convertRect:textField.frame toView:self.tableView];
NSIndexPath *indexPath = [[self.tableView indexPathsForRowsInRect:location] objectAtIndex:0];
// Save the contents of the text field into your array:
[yourArray replaceObjectAtIndex:indexPath.row withObject:textField.text];
}
Subclass UITableViewCell and add a UITextField in it's init method.
create a protocol for the custom cell class. when UITextField begin editing or end editing call the delegate's method.
in controller, implement the custom cell's protocol and set the indexpath row value to cell's tag in tableView:cellForRowAtIndexPath:
so every time you edit UITextField, you can know in controller which row is editing (via cell's tag)
The best way is to follow MVC and implement your model, which in your case is the textfield values. What you do is store those values in an array, and in your cellForIndexPath method you reference the correct value in the array. For example, the first row of the table (indexPath.row == 0) would retrieve the first element in the array. Changes in the textField would be recorded on the corresponding element in the array.

How can I get the value of a UITableViewCell that is not visible?

I have a table view cell that has many rows with a UITextView. In those UITextView the user can enter values.
The problem that I have is that if I want to get the value of a row when the row is hidden I get nil.
NSString *cellValue = ((UITextField *)[[[self.table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]] contentView] viewWithTag:[self.table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]].tag]).text;
I was thinking to save the value that the user enters in the UITextField, but if the user clicks a row that performs the calculate the delegate textFieldDidEndEditing is not being called.
I don't know how can I get this values in a secure way.
You should target the data source that is behind the UITableView. The UITableView is just a display mechanism and not the primary resource for getting to data. You can get the index for the cell based on the title, but then I would look tot he data source to get your actual data. Better yet I would just handle all operations against the data source itself if that is possible. When you build the table initially your data source is a list or array of some kind I am sure so you can just use it. You may need to create a variable on the view and retain the list/array there to make sure you have it and you should be all set.
Good Luck!
As mentioned, you would generally use the UITableView data source to access this.
That said, you are probably not seeing the data you expect because the you are using dequeued cells. If the number of cells is small, you could consider caching your own cells in an NSDictionary whose keys are some combination of the section and row, and accessing their view hierarchy via your own cache.
You can access rows that are not currently displayed by directly referring the tableView's dataSource with the wanted IndexPath.
You can achieve this by doing something like this (when using Swift 3):
UITableViewCell cell = self.tableView(self.tableView, cellForRowAt: indexPath)
What is happening most likely is your reusing cells. So when your cell with the textfield goes off screen it gets reused so you cannot access it anymore. What you will need to do is grab the value out the the textfield right after they enter it and store it somewhere. You can use – textField:shouldChangeCharactersInRange:replacementString: so that when ever the user enters a character into the textfield you can save the string.

Resources