I use the codes below to add textfield to each tableview cell,
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger row=[indexPath row];
static NSString *SimpleTableIdentifier1 = #"CellTableIdentifier";
//if I change the code to [NSString *SimpleTableIdentifier1 =NSString stringWithFormat:#"CellTableIdentifier%d",row]; everything is fine
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier: SimpleTableIdentifier1 ];
if (cell == nil){
CGRect cellframe=CGRectMake(0, 0, 200, 60);
cell=[[[UITableViewCell alloc] initWithFrame: cellframe reuseIdentifier:SimpleTableIdentifier1] autorelease];
UITextField * textfieldCell =[[UITextField alloc]init];
textfieldCell.frame = CGRectMake(100.0f,20.0f,60.0f,26.0f) ;
[textfieldCell setDelegate:self];
[textfieldCell setTag:40000+row];//add row value here for later use,
[cell.contentView addSubview:textfieldCell];
[textfieldCell release];
}
UITextField *textfieldCell ;
textfieldCell =(UITextField*)[cell.contentView viewWithTag:40000+row];
textfieldCell.text=[ NSMutableString stringWithString:#"aaa1"];
DebugLog(#"---------%#",textfieldCell.text);
return cell;
}
textfieldCell.text sometimes displays null rather than my expectation value of 'aaa1'
this means that the code line at:
textfieldCell =(UITextField*)[cell.contentView viewWithTag:40000+row];
sometimes returns nil, try to fix this confused result but failed
Your comment welcome
This dequeueReusableCellWithIdentifier
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier: SimpleTableIdentifier1 ];
returns a non nil cell Docs
This method dequeues an existing cell if one is available or creates a new one using the class or nib file you previously registered. If no cell is available for reuse and you did not register a class or nib file, this method returns nil
that may has a textField with different tag resulting in nil for
UITextField *textfieldCell ;
as these 2 lines
// here rhs may be nil
textfieldCell =(UITextField*)[cell.contentView viewWithTag:40000+row];
textfieldCell.text=[ NSMutableString stringWithString:#"aaa1"];
has no effect with nil textfield , also it's shocking how you still work with MRC ( Manual reference counting ) here
[textfieldCell release];
please update to ARC ( Automatic reference counting ) which will removes your worries about memory management issues
UITableView uses a dynamic pool of cells for performance reasons. The only cells which are actually active are those which are currently displayed on the screen. Any cells which are scrolled out of view are removed from the view and returned to a pool identified by, in your case, "SimpleTableIdentifier1"
The method dequeueReusableCell retrieves a cell from the pool, if one is available, or creates one if a class or XIB has been registered, otherwise returns nil.
The upshot of this is that your code may return a previously created cell which has been used for some other row earlier. The tag which exists for that cell will most likely relate to some other row. This will all depend on the scrolling of rows on and off screen.
Changing the identifier to "CellTableIdentifier"%row causes each cell to be allocated from its own pool and avoids this conflict. However that will completely defeat the purpose of reusing cells.
Since you are using tag just to locate the text field within the cell you do not need to make the tag unique across all cells of the table. Just use a simple constant ie. 4000 not 4000 + row. viewWithTag will return the view within the hierarchy which matches, and you are starting at the content view anyway.
Having said all that, the structure of your code is really not ideal. The better approach is to define your cell as a class, the layout in the storyboard, and access the text field directly as a member of the class.
Related
I am asking very basic doubt belongs to Tableview, I created tableview programmatically without Storyboard/Xib.
The tableView numberOfRowsInSection it will return 14, the cell view fully dynamic and each cell height will different from one another.
My questaion is in tableview delegate method
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell=(UITableViewCell *)[tableview dequeueReusableCellWithIdentifier:cellIdentifer];
if (cell == nil )
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifer];
// how many time entering this loop
}
// ( adding subview to cell view).
cell==nil means need to enter loop right. Depending on what parameter cell object become nil? how many times it will enter, is it once? not at all.
When I checked, it entered 6 times.
if I use cellIdentifier, it will enter 14 times because Identifier different and every time it will create space for cell, its right because each time name will different and while scrolling it will reused.
NSString *cellIdentifer= [NSString stringWithFormat:#"%ld,%ld",(long)indexPath.section,(long)indexPath.row];
which basis it will enter 6 times. why not one's or 14 times. Please suggest what I did wrong. Because if used #"Cell" identifier, while scrolling repeatedly view will overlap. If I used second one cell view object will not overlaps & looks like perfect, but device memory size will increase
ref by https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/index.html
If I right got your question you need register your class in viewDidLoad method or loadView where you created a tableview like that [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier]; for right reuse in tableview
Code : 1
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell=(UITableViewCell *)[tableview dequeueReusableCellWithIdentifier:cellIdentifer];
if (cell == nil ){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifer];
// how many time entering this loop
}
In above code the loop will execute as number of visible rows in your tableview at first time. After that it will reuse cell as the cellIdentifier is same Cell and you need to update cell data as per indexpath.
it means if your table display 6 rows then it will execute for 6 times. Change rowheight and you can check.
Code : 2
NSString *cellIdentifer= [NSString stringWithFormat:#"%ld,%ld",(long)indexPath.section,(long)indexPath.row];
for above code the loop will execute total number of rows you have declared, Because it will create new cell for each indexpath.
If we give same Identifier to all cells, Disappearing cell uses the memory of Appearing cell. But, If we give different Identifier then every cell will have its own memory location and shows data perfectly.
Now suppose we have 1000 or more records to load in Table-view. If we will give different Identifiers, there will be lots of allocations in memory. This is the benefit of re-using cells.
Why 6 times ?
Because, if you give same identifiers, table will re-use cells. Maximum number of cells visible at the moment, are allocated at first. Then on scroll, appearing cell uses a memory location of a disappearing cell (cell dequeuing). So, every time you scroll, new cells are not allocated. Instead, already allocated cells are re-used
Why 14 times ?
Because, every cell has different identifier in this case. Every cell will have its own memory location.
Remember
Add subviews inside cell nil condition. Cell specific content should be assigned outside nil condition. Have a look at following code snippet:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"MY_CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
/* Everything that is similar in all cells should be defined here
like background colors, label colors, indentation etc. */
}
/* Everything that is row specific should go here
like label text, progress view progress etc. */
return cell;
}
With same identifier:
When table cell is going to disappeared then this cell will be added in stack and these are reusable.
Now when we are going to show a cell then:
If we are using same identifier then controller will check that cell is available in stack with same identifier.
If yes, then we will get a table cell which is already used and UI was already set for this cell. So we need to reset UI before using it.
If not, then it will create new cell and trying to use it.
In your case I think 4-5 table cell is visible at a time so it is creating 6 table cell and reusing those cell.
With different identifier:
Table cell cell will not available in stack at creating cell for different indexpath. So it will create new one. so cell method will be called 14 times.
I have been searching and reading all over but couldn't find any conclusive method to achieve what I want to and hope to find help here...
I have a UITableView which allows the user to add multiple Flavours and Percentages to a Recipe. I have implemented the method to add or delete rows of Flavours with a custom Cell / Nib and it works perfectly well.
The issue I'm facing now, is how to retrieve the values the user has provided per added row.
(Edit for Clarity: My problem is not the populating of data, but only the dynamic reading of all data so I can save it)
I do manage to get the values for the visible rows (I do understand how the Reuseidentifier and the Tableview works, per se that for memory management's sake, iOS only keeps track of the visible rows), but not the hidden ones.
I assume in theory that I have to create an Array of Cells outside of 'cellForRowAtIndexPath' which maintains all cells. But then I'm facing another conceptual problem that my custom Nib / cell doesn't show.... basically:
How can I then use / register a nib without using the dequeingidentifier
Or in General, how can I solve the overall problem to be able an read all user entered values per row
Here the code I'm using within my cellForRowAtIndexPath. As mentioned adding and remove cell works like a charm, that isn't the issue...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RecipeFlavourTableViewCell *cell;
int section = (int)indexPath.section;
if(section==0)
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
if(!cell){
[tableView registerNib:[UINib nibWithNibName:#"RecipeFlavourCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
I have seen some Libraries doing it (e.g. XLForm) but do not understand (also when checking their sources) how they iterate through the values and overcome this dequeuing problem...
Any help is highly appreciated
EDIT 2: here the code I'm using to iterate through the cells in order to save the data, but as said I can only iterate through the visible cells:
- (IBAction)saveRecipe:(id)sender {
NSInteger dynamicRows = [self.tableView numberOfRowsInSection:1];
for (int i=0; i<dynamicRows; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:1];
RecipeFlavourTableViewCell *cell = (RecipeFlavourTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
NSLog(cell.flavour.text);
}
}
After 2 days of searching I finally came up with a solid solution. In case someone bumps into the same problem of dynamic forms with a tableview, here the solution:
As we understand, what ever cell is created in cellForRowAtIndexPath, it only persists as long as it is displayed. As soon as you scroll and the cell disappears, it gets automatically thrown out of memory. This behaviour makes it impossible to iterate through all cells at a later stages.
The steps to follow in order to make it work are as follows:
Preparation
Create an NSObject with all properties you want to persist in one form cell (-> cellObject)
In the ViewDidLoad of your controller create a NSMutableArray which will contain the cellObjects (-cellsArray)
Add as many cellObjects to the cellsArray as you initially want to appear in the Tableview
In numberOfRowsInSection return the count of you cellsArray
In the cellForRowAtIndexPath build your cells as usual BUT add a Textfield Delegate (self) to every Textfield in a cell
TextField Delegate
Implement:
- (void)textFieldDidEndEditing:(UITextField *)textField
and update your cellsArray Objects every time a Textfield ends editing. Per se, get the cellObject for the row and edit the properties with the value of the TextField
Add Row
When ever you add a row, just add an empty cellObject to your cellsArray and use the beginUpdates / insertRowsAtIndexPaths / endUpdates on your tableView (NOT reloadData as the already typed in data would get lost). Also add the following at the very beginning of your addRow method, as you want to make sure that if the user adds a row while editing a textfield, the latter gets persisted as well:
[self.view.window endEditing: YES];
Remove Row
Same as Add Row just reverse, remove the cellObject from your cellsArray and use deleteRowsAtIndexPaths on your tableView
Save Data
Now comes the trick: since you ought to always persist your data when a field ends editing mode, there is one case you need to cover: What if the user pushes "Save" when the focus is set on one TextField? Well at the very beginning of your Save Action insert the following:
[self.view.window endEditing: YES];
This make sure the the textFieldEndEditing will be triggered one last time for the current textField and that its data will also be persisted.
Finally iterate through your cellsArray and do whatever you want with it (validate, save etc)...
That's it, hope this can help anyone else as I couldn't find any valuable explanation anywhere else...
Lets assume that you have an NSArray and that it contains data you want to show. Your code should look something like this:
// Add this property to the class and fill it in with data you want to show
#property NSArray flavourElements;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RecipeFlavourTableViewCell *cell;
int section = (int)indexPath.section;
if(section==0)
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
if(!cell){
[tableView registerNib:[UINib nibWithNibName:#"RecipeFlavourCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
// At this point your cell is ready for showing
// And you can change values in it by getting element from array that contains data
cell.flavorTextField = flavourElements[indexPath.row].flavour
cell.precentageTextField = flavourElements[indexPath.row].precentage
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
I wrote a code for accessing elements without knowing what you actually have, so you will need to adjust it a little bit to fit your app.
I have a UITableView that I want to alter some of the static cells after I do other processing. I have outlets set up for the cells that I want to modify, but when I look at them using NSLog, they show nil, which indicates to me that I don't have the correct cell. For instance, in the image below I want to add the start time to the label just like I did for Date (date was done when creating the cells for which I got the current date),
I tap on the disclosure indicator which takes me to another scene (this was created in Storyboard, using segues to get from one scene to another) where I get the two times I need. I then return to the main scene (shown) and try to alter the Start Time label, but nothing happens. A NSLog of the label prior to trying to alter it returns this:
oStartTimeCell.textLabel.text: (null)
I have read in one of the Apple docs that this textfield is read-only. If that is true in this case, is there a way I can reload the cells with the updated information? Or is there another way to do this?
You're using the wrong approach. You should not create a reference to a cell using an outlet. Once the cell moves out of the visible view, the outlet will either be null or contain garbage data. Even if (in your situation) the cell will never move out of view, I think it shows you're trying to use a UITableView in a way that was not meant to be.
Instead put the data you want to display in your cells in a dataSource, e.g. an array.
The tableView should use the dataSource to configure the values displayed in the textLabels of the cells. Once you want to update the text displayed in the cells, change the values in the dataSource and call reloadData on the tableView to force the tableView to call -tableView:cellForRowAtIndexPath: and related UITableViewDataSource methods.
Try to create an IBOutlet for each cell and connect it:
IBOutlet UITableViewCell *cell1;
IBOutlet UITableViewCell *cell2;
IBOutlet UITableViewCell *cell3;
And also change your method to:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(indexPath.row == 0) return cell1;
if(indexPath.row == 1) return cell2;
if(indexPath.row == 2) return cell3;
if (cell == nil) {
//create cell;
}
return cell;
}
Are you using a UILabel to display the text ? . If you are just create an outlet to the UIlabel and update it any method like cellForRwoAtIndexPath or didSelectRowAtIndexPath etc that is called after you tableView is loaded.
If you are not using a UILabel and just using cell.textLabel you could do something like
cell.textLabel.text = #"ChangedText" in cellForRowAtIndexPathMethod. Make sure you are editing the required cell by checking indexPath.row
Do [tableView reloadData] to call cellForRowAtIndexPath.
I have a UITableViewController with prototype cells containing UITextFields. To configure these custome cells, I've created a UITableViewCell subclass. I've conected the textField to the cell subclass via an outlet (nonatomic, weak).
On this subclass I've created a protocol for which the UITableViewController is its delegate so that everytime something changes in these textFields, the TableViewController knows about it. Basically I wanted this to save the values on the NSUserDefaults
Besides, in order to dynamically obtain values from these textFields, I can do something like this:
((TextFieldCell*)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]]).textField.text
It works ok most of the times. However when the textField is outside of the view because it has scrolled, the vaulue I get from textField.text is (null). As soon as it gets in the view again, everything goes back to normal.
I tried to change the outlet from weak to strong but to no avail.
I guess I could define some private NSStrings on the class, and fill them out when the delegate protocol gets called. The thing is that I wanted to get my code as generic as possible, keeping the need for private variables as low as possible, mostly to simplify the cell generation code.
Is there any other way to get the values of the textFields when they are outside of the view?
Thanks in advance!
But you know that UITableView only keeps Cells for the visible rect?
When a cell leaves the screen, and a new cell is needed for another cell moving into the visible area, the old cell is reused for the new content.
So there is not one cell for each row of your table view.
And if your table contains a lot data, there are far more rows than cells.
As Thyraz said, the UITableView only keeps cells for the visible rect -- and a reasonable buffer to allow for scrolling. Thats why 'reuse identifiers' are so very important, they indicate which cells can be used for which tables (critical when you have more than one table to worry about). Unfortunately, that doesn't answer your question by itself.
The responsibility for storing the contents of those textViews isn't on the UITableView's shoulders. It's your job to provide that data through the data source delegate protocols, and therefore you should be querying the data source for that information.
Edit: Which means that yes, you should be storing this data somewhere else, usually in the form of properties on the view controller class that contains the table view. I'd recommend the use of NSArray for the purpose, but you can also do it through dicts or even, at the last resort (and this is more a in theory you can do this, but it's an incredibly bad idea kind of thing), a series of properties. Personally, I almost always use NSArrays because they're structured in a manner appropriate to the problem, but you could theoretically do it other ways. (I've used a dict based structure exactly once, and that was a situation where my data was nested inside itself in a recursive structure)
UITableViewController doesn't keep cells around once off the screen. You can use the following pattern to get a previously used one as a memory management optimization, but you MUST assume that cells need to have the values reset on them every time they come onto the screen (even if dequeued) because there is no guarantee what the values will be.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier1 = #"Cell1";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
if( cell == nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier1] autorelease];
cell2.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell2.editingAccessoryType = UITableViewCellAccessoryNone;
}
switch( indexPath.section ) {
case first_Section:
if( row == 0 ) {
cell1.textLabel.text = #"Some Text";
cell1.accessoryView = [self myCustomViewControl];
cell = cell1;
}
... etc
}
}
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.