How to reuse uitableviewcell?? - iphone

Every time when scrolling the cells reload shouldn't be trigged.

Apple knows: http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/TableView_iPhone/TableViewCells/TableViewCells.html%23//apple_ref/doc/uid/TP40007451-CH7-SW19

Yes you are correct. I'll elaborate on Steven's post of the Apple documentation a little.
For cells not to be reloaded, first look at this method:
TimeZoneCell *timeZoneCell = (TimeZoneCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
It says "give me the existing view that represents a table cell". This is a great saving because if the user is scrolling up or down quickly, creating views can be quite expensive.
Of course, you can't re-use a cell if one hasn't been created in the first place. So, you need to handle that scenario. That's where this code comes in:
if (timeZoneCell == nil) {
timeZoneCell = [[[TimeZoneCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
timeZoneCell.frame = CGRectMake(0.0, 0.0, 320.0, ROW_HEIGHT);
}
It says, "is the cell nil?", i.e., have we not yet created a table cell? It then goes on to create one. That cell will subsequently be be re-used.
The actual contents of the cells will change, for example the label or whatever. However you will continue to re-use the existing objects.

Related

UITableView renames every eighth cell

I have a UITableView in my MainViewController. When a user taps a cell,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selectedRow = indexPath;
....
[self performSegueWithIdentifier:#"OtherViewControllerSegue" sender:self];
}
and they are taken to another UIViewController (let's call it OtherViewController). In OtherViewController, the name for the selected cell is set. When OtherViewController is dismissed, it updates the cell in MainViewController with the new name:
[[[mainvc.myTableView cellForRowAtIndexPath:mainvc.selectedRow] textLabel] setText:namecell.textField.text];
[self.navigationController popViewControllerAnimated:YES];
This all works fine until I have more cells than will fit on the screen. If there are more cells than will fit on the screen (8 for iPhone or 16 for iPad), then this will also set the name for every eighth or sixteenth cell respectively. Any ideas on what I am doing wrong?
Update:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [pointsTableView dequeueReusableCellWithIdentifier:#"myTableCell"];
return cell;
}
This is due to cell-reuse and you are mixing up your model with your view (in the MVC context).
A table-cell is a transient thing, once it goes off the screen it is reused (instead of creating new cells) when another cell is needed. This is what the dequeueReusableCellWithIdentifier: method does.
This means you can't store data in there and expect it to still be valid later on. In this example you are trying to store the name in the table cell. The reason to set a property (like the label text) on any view object is purely for display, not for storage. So to solve this problem you should maintain a list of objects in your model (this could be in separate classes or in an array in your mainvc object for example). Then in cellForRowAtIndexPath: you should set the label text every time - even when there should be no label you need to set it to nil or an empty string because the cells are re-used it might contain something from the last time it was used.
Update:
Instead of calling cellForRowAtIndexPath: yourself and setting its text, you should set the text in your model using a method or property in your controller and then tell the table view to reload that cell. The code might look something like this:
// This code is in where you want to set the text from
[mainvc setText:someText forIndexPath:indexPath];
.. and in your main view controller:
- (void)setText(NSString*)newText forIndexPath:(NSIndexPath*)indexPath
{
// Store the text in your model here...
...
// If the view is loaded, the table view should reload the cell.
if(self.isViewLoaded)
{
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
The table view will then call cellForRowAtIndexPath: where the text will be set correctly. This may seem a little convoluted at first, but when you get used to using the Model-View-Controller design pattern you will find that keeping the jobs of each MVC component separate like this will mean your code is tidier, easier to understand, has less bugs, is easier to update/extend, etc.
You're trying to store data (the new name) in a view (the cell's label). What's probably happening is that when you re-use cells in the data source's cellForRowAtIndexPath method, some of them are ones that have had this text set for them and it's still there.
The better idea is to make your changes in whatever array you use as cell information and then reload the table view to make the changes visible.
As I suppose, you shouldn't call cellForRowAtIndexPath by yourself. It can be called to create cell, not to change it.
You can update your table by passing needed string to the first view via delegate, for example. And on the event (user sets the name) you can update all table and set needed names to cells.
Hard to say exactly what the problem is, but one possible solution might be this:
Make sure that in your cellForRowAtIndexPath you are initializing the cells like this:
// Create the Cell
static NSString *recordCell = #"pickerTableCell";
cell = [tableView dequeueReusableCellWithIdentifier:recordCell];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:recordCell];
}
I know this is primarily a memory solution, but might gelp here too.
Also, look through your code and check how you are determining which cell is renamed. You could be accidentally calling the rename on more than one cell without realizing it

How to handle references to UITextFields in dynamic UITableView (iPhone)

I have a UITableView that is broken up into a user defined number of sections. Within each of these sections there are always 2 rows. Each of these two rows contains a UITextField which the user is able to edit.
What I need is a way of being able to access the data in these UITextFields at a later point. I was hoping it would be a simple problem however it's causing me a great deal of grief.
So far I have tried two approaches:
Attempt 1
I created two NSMutableArrays in which I added the UITextField objects to at index i (corresponding to the section it came from). I then tried to access the values by iterating through the array. This didn't work since the UITextFields kept getting wiped clean. Every-time I scroll down the table and the UITextField is out of view, when I go back it's contents have been wiped clean.
Attempt 2
I tried to get hold of the number of sections in the UITableView (this was fine). I then wanted to iterate through each section of the UITableView, recording the values in the rows of each. This is where I became unstuck since I'm not sure how to do this or if it's even possible.
I apologise if this is a naive question to ask, however I'm really struggling and would appreciate any advice.
Keep in mind that the text fields get reused as you scroll, so you don't really want to store references to them.
What you do instead, is to capture the information as it is entered. The easiest way to do this is to implement the textFieldDidEndEditing protocol method in the delegate.
The tricky part is figuring out which row the text field is in. The best way is to create a UITableViewCell subclass which has a NSIndexPath property. You can then set that when you configure the cell with tableview:willDisplayCell:forRowAtIndexPath:.
Then, in textFieldDidEndEditing, access the tableViewCell indexPath property through its superview. i.e.:
NSIndexPath indexPathOfParentCell = [(MyUITableViewCellSubclass *)self.superview indexPath];
Doing it this way allows you to know both the section and row of the cell.
Create your TextField in the cellForRow of the Table like so and give it a tag
UITextField * userField = [[[UITextField alloc] initWithFrame:CGRectMake(0, 12, self.view.frame.size.width -20, 20)] autorelease];
userField.tag = 1001;
userField.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:14];
userField.textAlignment = UITextAlignmentCenter;
userField.delegate = self;
userField.autocorrectionType = UITextAutocorrectionTypeNo;
userField.autocapitalizationType = UITextAutocapitalizationTypeNone;
userField.clearButtonMode = UITextFieldViewModeWhileEditing;
if (indexPath.row == 0)
[cell.contentView addSubview:userField];
then access the TextField like so:
UITextField *userField = (UITextField *)[[(UITableViewCell *)[(UITableView *)tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] contentView] viewWithTag:1001];
Your "Attempt 1" should work OK if you keep the text field's text in your arrays rather than the text field itself. (Anything that tries to make a view or control into a data object has a good chance of going wrong.)
Whatever acts as a data source for your table view should be able to re-populate the scrolled cells according to section and row if the content is stored separately.

Highlighting cell when selected

I have a tableView, and i can select multiple records. When i click more that 1 record the cell should get highlighted. i have attached my code below; What hapence now is that when i click on multiple records it higlights, but when i try to remove it (as in click the same row again), a different cell gets un-highlighted. Why is this ?
But if i replace the [cell setHighlighted:YES animated:YES/NO]; with [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; everything works properly. How can i fix this ?
I have added code in the didSelectRowAtIndexPath
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell accessoryType] == UITableViewCellAccessoryNone) {
[cell setHighlighted:YES animated:YES];
}
else {
[cell setHighlighted:NO animated:YES];
}
You might want to use UITableViewCellAccessoryCheckmark, as it is intended for this purpose.
According to apple developer documentation for selecting more than one row you should use .
cell.accessoryType = UITableViewCellAccessoryCheckmark;
and for Deselect
cell.accessoryType = UITableViewCellAccessoryNone;
As others have stated, highlighting is not the preferred way to do this. Users want a consistent experience across apps, which is why the Human Interface Guidelines recommend using a checkmark to indicate row selection: http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/mobilehig/UIElementGuidelines/UIElementGuidelines.html#//apple_ref/doc/uid/TP40006556-CH13-SW42 (Table 7-1)
Aside from the UI issues being raised, the problem you are experiencing may be due to your table cells being reused, and retaining the highlighted state from the record that was previously displayed in that cell.
I suggest you keep a separate record of indexes/id's for those highlighted. You should then always check against this, rather than the state of the cell itself. (You can use NSArray or other collection class depending on your needs).
In tableView:cellForRowAtIndexPath: you should also check this array and set the highlighted state accordingly.

UITableView cell redraw causes text to overlap

I do following in cellForRowAtIndexPath
NSString *cellIdentifier = #"Cell";
LibraryCell *cell = (LibraryCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell != nil) {
[cellIdentifier release];
[self setItems:cell forRowAtIndexPath:indexPath];
return cell;
}
cell = [[[LibraryCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier] autorelease];
[self setItems:cell forRowAtIndexPath:indexPath];
return cell;
[self setItems:cell forRowAtIndexPath:indexPath]; changes only some UILabel's value inside the corresponding cell.
So now when I edit UITableView and delete let say the first row, the UILabel's value is no redrawn completly, i.e. the old value remains and new value is drawn overlapping the old one.
Why this happens and how to fix it ?
I guess you misunderstood the cell identifier concept.
It is only used to distinguish what you might call "stamps", used to print a cell's appearance on the screen. So you would most probably only need a single cell identifier.
It helps the system to cache instances of the "stamps". When cellForRowAtIndexPath is called, you only have to pick which kind of stamp you want to use. If you created an instance of the correct one before (i. e. you get one back when asking for it using the cell identifier string), you only need to change the label texts etc. and then return it. In real life this might be likened to one of those date stamps where you can change the date by turning the little knobs on the stamp. This is what you would do by assigning a new text to the label contained in the cell.
You instead seem to be creating a stamp for each index in your model by concatenating the string value, effectively creating as many instances as there are rows in your model. Apart from being unnecessary it might also cause memory pressure and stuttering, because it counteracts all sorts of optimizations the UITableView has.
I recommend you read up on Apple's documentation or see iTunes U (here)
for the Stanford courses on iOS development. It gets explained very clearly there.
First of all, You should use the same name for your cellIdentifier
Please refer to the UITableView Class reference
http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:
If you cannot get a reusable cell, then create it. After that update your cell.
The code should be like this
LibraryCell *cell = (LibraryCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
// create a new cell
...
}
// Update cell
...
return cell;
Actually I figured out ! The problem was coming from setItems:forIndextPath method.
It was creating UILabel locally and releasing it. So every time it was drawn over the last text text. Making the UILable instance variable solved the issue.
PS. The code originally was not written by me, I'm just fixing it :)

Problems with UITableViewCellStyleValue1

I have the following code:
static NSString *CellIdentifier = #"Cell";
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.text = #"Publisher";
cell.detailTextLabel.text = #"This Is A Very Very Long String";
which results with the following look: (only first table row is relevant)
As it appears, the detail text is overlapping the "Publish" title so both strings are truncated.
What I wish is to have the "Publish" title neverc being truncated as follows:
Is this possible using UITableViewCellStyleValue1? I saw many posts suggesting to create a custom cell but is it really the only way?
Thanks,
Josh
You can try to set adjustsFontSizeToFitWidth property for cell's labels to YES and minimumFontSize property to an appropriate value. If it does not help - it seems you should use custom cells indeed.
I realize some time has passed since this post, so there have probably been updates to this functionality from the SDK. What I've found in Xcode v4.1 is UITableViewCellStyleValue1 now does what you wished. The textLabel.text doesn't truncate.
Using UITableViewCellStyleValue2 will truncate the title. Where the doc suggests
it functions as a heading or caption for the important information in
the more prominent, left-aligned detail text label.