I have a subclass of UITableViewCell that loads a nib on the parent ViewController viewDidLoad event - where the table view is located.
There's a UITextView inside this UITableViewCell.
The idea is that the user enters text in this textView, and when a button is pressed it will save this text (answers for a questionnaire) in NSUserDefaults.
My problem is that I cannot read the text from the textviews. When the user enters text and the table view is scrolled up and down, I can see the text is kept, so it must be stored in memory somewhere, this happens in cellForRowAtIndexPath.
For gathering the text I call this same method but the returned cell is nil. So it seems the text is kept when scrolling but if the method is called programmatically the cell is not returned.
I'm not sure how to keep track of this text, I have also tried the method didEndDisplayingCell but sometimes it doesn't get called so it's not reliable.
Nib registration:
UINib *nib = [UINib nibWithNibName:#"QuestionCell" bundle:nil];
[[self tableViewOutlet] registerNib:nib forCellReuseIdentifier:cellIdentifier];
cellForRowAtIndexPath:
QuestionCell *cell = [tableViewOutlet dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell.indexPath){
cell.indexPath = indexPath;//Setup cell ...
Trying to get the text inside the textView inside the cell (returns nil if not visible):
QuestionCell *cell = (QuestionCell *)[tableViewOutlet cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
You shouldn't rely on your tableview to retain textfields with the proper text entered. As soon a TableViewCell is scrolled off the screen, it'll get reused by the TableView and the value that the user has entered will likely be overwritten.
Instead, you should register your ViewController as the UITextView's delegate, and update your TableView's datasource object with every key press. Then, you'll be able to read the user's entered text off of that datasource object whenever you need.
Related
What is difference between
[tableView deselectRowAtIndexPath:indexPath animated:NO];
and
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexpath];
[cell setSelected:NO];
???
In a UITableView a cell does not match a specific piece of your data. It can (and should most of the time) be reused and it is nil when not visible.
deselectRowAtIndexPath will set the indexPath as 'not selected' in your tableview, so when you scroll back and forth to that cell, it will stay unselected, because you told your tableview that whatever the cell you display at that indexPath it should be unselected.
With UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexpath];
[cell setSelected:NO]; you set the cell as unselected. However that cell can be used for other pieces of data, and it can even be nil if this indexPath is not displayed.
First one is programmatically deselecting the cell. So it will un-highlight the cell if the user has selected it already.
As for the second bit of code, I believe that is just a pointer or reference to one of the cells in your table view. You can use this code to edit a cell OUTSIDE of any of the table view delegate methods. So if you wanted to edit/update a label on one of your cells but from a random method (not a table view delegate method), then you could use that code to reference the label text property.
I think you should also check out the Apple Developer Library website. It explains all the different table view method/properties/etc in lots of detail:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/
The concepts of "selected" is different for a tableview and for a cell. The tableview can have one or more rows selected that changes the state of the tableview, however changing the tableview cell selected state only affects its appearance and nothing else.
I've got a UITableView with several different elements added programmatically. The one I'm having trouble with is the UITextView that displays correctly with correct color, size, font, etc... I have a button in one cell that increases the size of the font in the UITextView in another cell. It works fine and has no issues. The numerical value is placed in a Plist, and when you leave the view with the table and come back the size changes perfectly.
I've placed a reloadData in the button which does reload the table and gives the textView new size and resizes it to fit the new content plus resizes the cell perfectly. The issue I'm having is that when the reloadData is called, the old textView remains. So I have two texts, at two different sizes, or three or four and so on. How can I remove the previous textView when it's not set to global?
Everything is set up exactly how one would expect:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
// cell with textView. Everything is instanced and created for just that cell with tags
UITextView *t = [self setSizeAndTextOfTextView];
[cell.contentView addSubview:t];
// cell with button. simple, alloc's and init inside cell. Calls method in same class
cell.contentView addSubview:button];
//method to increases font size
write to Plist the new size
[self.tableView reloadData]; <-- tableView is iboutlet that does reload table
How are you getting the cell in the first place? Are you reusing? If you are you don't want to add the textview as a subview again you want to retrieve the existing one and adjust it
UPDATE:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
if ([cell.contentView viewWithTag:1]) {
UITextView *t = (UITextView *)[cell.contentView viewWithTag:1];
//This version will take an existing textview and just resize it
[self setSizeAndTextOfTextView:t];
} else {
//This version creates a new text view
UITextView *t = [self setSizeAndTextOfTextView];
t.tag = 1
[cell.contentView addSubview:t];
}
You'll probably need to do something similar with you button as well
The reloadData won't wipe the existing cells, just the data displayed, so you'll get an old one to reuse
You may consider creating a custom subclass of UITableViewCell and associate that with your cell identifier. In your subclass, override the prepareForReuse method to set the cell back to a neutral state. Since cell objects are reused but are only initialized once, prepareForReuse is available to restore an already existing cell to its freshly initialized state.
In my application I present a UITableViewController with custom UITableViewCells. Each cell has been created throught .xib file. I decided to take this way because each cell displays different elements. Elements are a UILabel, a UITextField and a UIImageView. Each cell displays info about products.
The code I use to present the custom cell is the following:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:kCellTableIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"CustomTableViewCell" owner:self options:nil];
cell = self.customTableViewCellOutlet;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundView = [[[BackgroundCell alloc] init] autorelease];
cell.selectedBackgroundView = [[[BackgroundCell alloc] init] autorelease];
}
// configure the cell with data
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
where self.customTableViewCellOutlet is an outlet to the cell .xib file with an assign retain policy.
The code works very well.
Now I need to implement the following two specifictions:
1) First, when the user taps in a UITextField, that element becomes editable. When the user taps outside the modified value is saved and the UITextField becomes not editable.
For this first specification I thought about the following mechanism. The File's owner for my .xib file could be UITableViewController. Doing this, my UITableViewController could implement UITextFieldDelegate and listen for UITextField events. The only aspect that is not clear for me is how to listen when the user taps outside the UITextField.
2) When the user tap in a row, the cell has to change its content. In particular, each cell has to change its content. From displaying a product (good) to presenting a detailed description for that product. In other words, when the user tap the cell I need tho swicth from a view (that one that displays raws info) to another one.
Here I don't know how to proceed.
Could you provide me some suggestions? Thank in you in advance.
implementing the textField Delegate in UITableViewController or UITableViewCell depends on the requirements.
wether u wanted the written text to be in array? then u can set index as tag of textField. Then get the text in delegate and add to the array then reload the table.
Another thing u can add a tap gesture on the cell, which returns u the row, when tapped outside the textField..
I have a UITableViewController with UITextfield inside the tableview cells. If I scroll the table view, the user entered data in the textfields disappears. I tried to add the textfield data to a NSMutableArray but it still didn't work. Any help please.
When cellForRowAtIndexPath: is called, the cell you return has to be completely filled in with whatever data you want to show. So, if the cell includes a UITextfield, you'll need to set it's text property to the right value for that row in your data.
When a table cell disappears off the top or bottom of the screen, the UITableViewCell itself becomes available for re-use. (As you scroll, cells disappear, and new cells appear, but the UITableView class is re-using the UITableViewCell objects.) In cellForRowAtIndexPath: when you get a cached cell to use, you have to be sure to setup everything you want it to show for the row in question, otherwise you might see some odd behavior in your table.
Does this help?
EDIT:
Here's an example of the typical pattern used in cellForRowAtIndexPath:. Notice the use of dequeueReusableCellWithIdentifier:. That method returns a previously allocated but not in use UITableViewCell, if there is one. Notice further that if no cached cell is returned, the code creates a new one, and sets it up (with stuff that is independent of anything that might be row specific). Following that, you'd setup the cell as you need it for the row in question.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SearchResultsCellIdentifier = #"SearchResultsCellIdentifer";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:SearchResultsCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:SearchResultsCellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Your row-specific setup of the cell here
// ...
return cell;
}
Check the docs for specifics about these methods. There are LOTS of examples from Apple and elsewhere about how to implement tableViews.
I have a ViewController that contains a UITableView in which the cells are created via a custom subview (so that each cell will have a label and textfield). The subview includes TextField delegate methods that get fired on textFieldDidBeginEditing, textFieldDidEndEditing, etc. The UITableView caches the cells that are visible and destroys them when a cell is no longer visible. The problem I have is when the user taps on a TextField in a cell and then scrolls the table so that the cell being "edited" is out of view and then taps on a new cell to edit it, the original cell's textFieldDidEndEditing delegate method is called, causing a "respondsToSelector:]: message sent to deallocated instance" error because the original/first TextField has been destroyed by the UITableView when it was scrolled out of the visible area.
Has anyone dealt with this issue before and found a solution so that I can keep my textFieldDidEndEditing methods for proper handing of the data the user's input without having the now-invisible cells destroyed?
EDIT: I just found this in the UITableView Class Reference doc: "Avoid relayout of content. When reusing cells with custom subviews, refrain from laying out those subviews each time the table view requests a cell. Lay out the subviews once, when the cell is created."
This is, I think, the root of my issue. But I am not sure how to follow this advice. Help...
"The UITableView caches the cells that are visible and destroys them when a cell is no longer visible." That's true only if you don't provide a reuse identifier.
Assuming that you're working on a detail view where each cell is presenting (conceptually at least) a property of some model object, you can assign each cell a unique reuse identifier. You can set this directly in Interface Builder, or, if you're creating the cells programmatically, by passing it as an argument to initWithStyle:reuseIdentifier: or initWithFrame:reuseIdentifier.
Given a reuse identifier, the table view will cache the cells for its entire lifetime. To get a cell from the cache use
cell = [tableView dequeueReusableCellWithIdentifier:#"Some Identifier You Made Up"];
Again, use different identifiers to distinguish unique cells, if necessary.
An alternative for a detail view that has a fixed number of unique cells is to store a reference to each cell in an instance variable (or store the whole group of them in a collection). As long as you retain the instance variables, the cells won't be deallocated even if you didn't bother providing reuse identifiers.
Im wondering if this could be your issue:
if (cell == nil) {
// dont do this -->cell = [[[customCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
NSArray *topLevelsObjects = [[NSBundle mainBundle] loadNibNamed:#"NewUserCustomCell" owner:nil options:nil];
for (id currentObject in topLevelsObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (customCell *) currentObject;
break;
}
}
I don't know if you found a solution yet. But I faced the same problem, and I fixed it by setting the delegate of the textfield to nil in dealloc of my custom field, before releasing it. This way the delegate textFieldDidEndEditing is not called on the deallocated cell.
I was also not using a reuse-identifier.
- (void)dealloc {
[textField_ setDelegate:nil];
[textField_ release];
[super dealloc];
}