UITableViewCell - view showing data on top of data when being reused - iphone

I'm using a custom UITableViewCell. They load up great, but when they get reused instead of replacing the text in the labels, they are some how writing over the top of them. Any ideas how to stop this behaviour?
I assume I'm not reseting the view correctly before it gets reused. I'm currently empty the labels so they have just a blank #"" string. But I still get the old text plus the new (very messy).
I'm certain there's an obvious solution to this (presently i just don't reuse the cell, but this isn't best practice and is slow on old devices), so if someone can help I'd be very grateful.
Thanks
ED
As requested here is the method for amending the cell
static NSString *CellIdentifier = #"Cell";
TransactionCellView *cell = (TransactionCellView *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"TransactionCellView" owner:self options:nil];
cell = tblCell;
}
[cell resetCell]; // clears the labels
[cell setData:[dataArray objectAtIndex:[indexPath row]]; // replaces the data and updates the labels
return cell;
}

Solved this issue!!! Amazingly simple when you know what the problem is/was.
I was using IB and "Clear Graphics Context" was not selected on my UILabels. So that is why the next text was just overlaying the old.
THanks guys for trying to help.

I suspect that you are adding the label as a subview programatically in your cellForRowAtIndexPath method. You need to make sure that you are not adding that subview every time the cell is recycled. Instead create the label only when creating a new cell (not when recycling a cell) then assign a tag value to the label and then in the future, when it gets recycled, retrieve the label by tag value and change its text.

You are correct in that the cells are being reused. Your code should be set up roughly using the following pattern:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString* identifier = #"someidentifier";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
// create the cell and add all subviews to it
} else {
// update the cell and access appropriate subviews to modify what is displayed
}
return cell;
}
The cell will be created the first time the identifier is used. For all subsequent requests, the cell is pulled from the UITableView cache (via dequeueReusableCellWithIdentifier), and you can then access its subviews either by tag, index, type, or whatever mechanism you choose.
Somewhat related is that you can have multiple cell identifiers, which allows you to create multiple instances of different cells depending on the data that you have. In one of my projects, I have 4 different cells, each dependent upon the number of lines of data that they will display (anywhere between 1 and 4). This helps ensure a smooth scrolling experience regardless of how many lines the cell has since the renderer doesn't have to worry about dynamically changing the height of the cell on the fly.

Try this..
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
for (UIView* subView in cell.contentView.subviews)
{
[subView removeFromSuperview];
}
// Your Code to customize cell goes here.
}

Related

iPhone: smooth scroll through the UITableView

I have a UITableView, and custom cells on it. On cell I have a UILabel, but before I set text to UILabel I did really hard work on text...like find the text in another text, highlight some words on it, and only then I set it to label. So when I scroll my list, it has delay because of this hard work. Any idea how to improve performance ? Maybe to do all hard work in another thread ??
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath {
static NSString *customCellIdentifier =
#"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
customCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomTableRow"
owner:self options:nil];
if (nib.count > 0) {
cell = self.customTableRow;
}
}
self.myLabel.text = [self giveMeTheTextThatINeed];
return cell;
}
[self giveMeTheTextThatINeed] - did a hard work on text that takes some time.
Make a new thread for every cell. this thread calls [self giveMeTheTextThatINeed:indexPath], and resets the label(s) in the cell. I'm assuming you can't get your data any faster, so you want to maintain the scrolling in the table and spin the hard work out to the thread. When the thread is finished, update the cell. You see this a lot in cells with a thumbnail image where the thumbnail only gets uploaded after a while, and is blank or has a placeholder there first.
Any way for you to precompute the values you'll need? In other words, start doing your "hard work" (in another thread) when the app starts, and store it somewhere so that, if it's ready, you can just grab it when the table view asks for it. It's hard to answer without more detail about what the hard work is and how much data we're talking about.
I don't know about just doing the hard work on another thread as you suggested, since you still have to give something to tableView:cellForRowAtIndexPath:. I suppose you could return some kind of template cell at first, and then update it with reloadRowsAtIndexPaths:withRowAnimation: when the hard work is done.
I believe the method giveMeTheTextThatINeed needs to take the current cell as one of the parameters (or another parameter dependent on the cell content), e.g.: [self giveMeTheTextThatINeed:indexPath]. Otherwise you could store the text as an instance variable and set it in all cells from that variable.
So, with that in mind, the easiest way is to store the result of the computation in an additional dictionary, where indexPath (or the other parameter) would be the key:
self.myLabel.text = [self->myDictionary objectForKey:indexPath];
Now, you could either pre-populate that dictionary before the cells are drawn (e.g. in viewWillAppear), or cache them once they are calculated so that they are not recalculated when the cells are scrolled, e.g.:
NSString* calculatedText = [self->myDictionary objectForKey:indexPath];
if(calculatedText == nil)
{
calculatedText = [self giveMeTheTextThatINeed:indexPath];
[self->myDicationary setValue:calculatedText forKey:indexPath];
}
self.myLabel.text = calculatedText;

Problem in displaying properly in tableView cell by using label

In my application I parsed the data through NSXMLParser and made separate class to store that data from which i usually display the data. Everything works fine in simulator except the the title which is display in table cell with image. Images appears properly but the title not appear properly.
This is my code:-
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"TableCell3" owner:self options:NULL];
cell = nibLoadedCell;
}
NewsInfo *aNewsInfo = [appDelegate.newsArray objectAtIndex:indexPath.row];
titleLabel.text = aNewsInfo.title;
imageLabel.image = aNewsInfo.smallImageData;
return cell;
}
and in this way i configured my TableCell3.xib
Now i want my title data in 2 rows in label of the TableCell3.
I already used both the way through xib attributes settings or through code.
Code which i used instead of this xib attributes settings are:-
titleLabel.lineBreakMode = UILineBreakModeWordWrap;
titleLabel.numberOfLines = 2;
titleLabel.text = aNewsInfo.title;
Now Can any one help me to figure out this prob i want to display my title in two rows of the table view cell.
Thanks in Advance.
My Data is showing in this way...![enter image description here][2]
So i want to display this title fully it show's partially right now...
i want to display in two lines...
My guess is that you need to enlarge the label height.
Maybe the label height i not large enough to display 2 lines of text and that's could be the reason that you see only 1 line.
in interface builder try to enlarge the label height.
and maybe i am wrong ?
Good luck
Shani
Adjust frame of an UiLabel to have more space for two lines or decrease font size.

All UITableCells rendered at once... why?

I'm extremely confused by the proper behavior of UITableView cell rendering. Here's the situation:
I have a list of 250 items that are loading into a table view, each with an image. To optimize the image download, I followed along with Apple's LazyTableImages sample code... pretty much following it exactly. Really good system... for reference, here's the cell renderer within the Apple sample code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// customize the appearance of table view cells
//
static NSString *CellIdentifier = #"LazyTableCell";
static NSString *PlaceholderCellIdentifier = #"PlaceholderCell";
// add a placeholder cell while waiting on table data
int nodeCount = [self.entries count];
if (nodeCount == 0 && indexPath.row == 0)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:PlaceholderCellIdentifier] autorelease];
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.detailTextLabel.text = #"Loading…";
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Leave cells empty if there's no data yet
if (nodeCount > 0)
{
// Set up the cell...
AppRecord *appRecord = [self.entries objectAtIndex:indexPath.row];
cell.textLabel.text = appRecord.appName;
cell.detailTextLabel.text = appRecord.artist;
// Only load cached images; defer new downloads until scrolling ends
if (!appRecord.appIcon)
{
if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
{
[self startIconDownload:appRecord forIndexPath:indexPath];
}
// if a download is deferred or in progress, return a placeholder image
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
else
{
cell.imageView.image = appRecord.appIcon;
}
}
return cell;
}
So – my implementation of Apple's LazyTableImages system has one crucial flaw: it starts all downloads for all images immediately. Now, if I remove this line:
//[self startIconDownload:appRecord forIndexPath:indexPath];
Then the system behaves exactly like you would expect: new images load as their placeholders scroll into view. However, the initial view cells do not automatically load their images without that prompt in the cell renderer. So, I have a problem: with the prompt in the cell renderer, all images load at once. Without the prompt, the initial view doesn't load. Now, this works fine in Apple sample code, which got me wondering what was going on with mine. It's almost like it was building all cells up front rather than just the 8 or so that would appear within the display. So, I got looking into it, and this is indeed the case... my table is building 250 unique cells! I didn't think the UITableView worked like this, I guess I thought it only built as many items as were needed to populate the table. Is this the case, or is it correct that it would build all 250 cells up front?
Also – related question: I've tried to compare my implementation against the Apple LazyTableImages sample, but have discovered that NSLog appears to be disabled within the Apple sample code (which makes direct behavior comparisons extremely difficult). Is that just a simple publish setting somewhere, or has Apple somehow locked down their samples so that you can't log output at runtime?
Thanks!
You certainly should not have an actual UITableViewCell instance for every row in the table. You should only see a few more instances than are visible in the UI. That is where your problem is. It doesn't have anything to do with the loading of images.
The only time I've seen a large number of cells instantiated when the cells where dequeued was when a coder had altered the frame of the tableview to make it much larger than the screen. The tableview retains enough cells from being dequeued to carpet its own frame regardless of what is visible. If the frame is to big then you get a lot of cells in queue.
NSLog does work in Apple examples so if you can't get NSLog output you've got something weird going on with the dev tools themselves.
You might want to shutdown Xcode and the simulator and restart and see if that clears up the odd behavior.
Oh my... mustISignUp is absolutely correct in saying "you definitely have a deeper underlying problem". I have variable-height table rows, and I was doing all height calculation and data storage on the rows themselves rather than on the data model that populated them. As a result, ALL cells were being created and populated by my heightForRowAtIndexPath method which was reading cell height from the cell objects. SO – lesson learned.
Thanks mustISignUp, and I love your username.
NSLog definitely isn't disabled within the Apple sample code. I don't know why you can't see it but you definitely have a deeper underlying problem.
Anyway, for your comparison:- if you have 6 rows on screen -tableView: cellForRowAtIndexPath: is called 6 times with index [0, 0] - [0, 5].
So, is that what you are seeing? How many times is -cellForRowAtIndexPath being called?

Where to place the update logic for a custom UITableViewCell? (iPhone)

I just have a quick "best practice" question regarding custom cells in a UITableView.
My problem arises when I have build a custom cell in the if(cell == nil) block in the
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
(1) If I build labels and set the text property of these inside the (cell == nil) block; only the cells visible on the screen will have the correct text in them, the rest will just be reused.
(2) Ok, so I try moving the building of the custom cell outside the block. This of course results in my cells getting redrawn over and over again each time a user scrolls… and on top of each other.
(3) I also tried placing the drawing of the custom cell (labels, graphics etc.) inside the (cell == nil) block, but setting the labels.text property after the block. This can't be done as my
UILabels then can't be accessed from outside the block they were instantiated in. I could do some crazy for(UILabel *l in cell.contentView.subviews ) but that would require an if statement for each label and probably some tags to identify them with.
(4) I could then declare all my labels before the (cell == nil) block and then just do the instantiation and drawing inside the block and setting the text property after the (cell == nil) block. But as I passed 10 UIlabels this looks and feels really messy, having them spread out like this inside and outside the (cell == nil) block, plus they would be declared each time the table has to "refresh".
The reason I ask is because when using 2-4 labels I have always found a way around it, but this time I need 12 custom UILabels set up and I would like to do it so that I have to redraw the least amount of graphics, as there probably will be a lot of cells.
I have seen examples where people and the Apple docs uses a:
- (void) configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
But I can't really see how that would help me out:)
Hope someone could point out a "best practice" for these sort of things.
Thank you!
Your option 3 is the way to go. Create the cell with all its subviews whenever a new cell is required (i.e., in the if (cell == nil) block) and set the contents of your labels after the block.
If you use a custom UITableViewCell subclass, you could declare a property for each label and then access them with cell.myLabelX outside the block. If not, I would use the tag property to make the connection. At cell creation, assign a unique tag to each label:
UILabel *label1 = [[UILabel alloc] initWithFrame:...];
label1.tag = MYLABEL1TAG;
[cell addSubview:label1];
...
Outside the block, retrieve the labels through their tags:
UILabel *label1 = (UILabel *)[cell viewWithTag:MYLABEL1TAG];
label.text = ...
This is also how Apple does it in many places in their sample code (at least that's how they did it when I last looked).

What does "MyIdentifier" mean in Objective-c or iPhone programming

I am puzzled at the following line "static NSString *MyIdentifier = #"MyIdentifier";" in the method: cellForRowAtIndexPath
What does that line do?
Is it just creating a random pointer to an NSString object and assigning it the string?
Why is it being called MyIdentifier, I have seen this in many examples.
#import "AddToFavorites.h"
#implementation AddToFavorites
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:MyIdentifier] autorelease];
}
// Configure the cell
return cell;
}
#end
Here is another example, this one has a different string, CellIdentifier.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TimeZoneCell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self tableviewCellWithReuseIdentifier:CellIdentifier];
}
[self configureCell:cell forIndexPath:indexPath];
return cell;
}
UITableViews can automatically reuse cells to save on memory. To take advantage of this, you must specify a "reuse identifier" which is used by the UITableView to be able to look up existing cells ("dequeueReusbaleCellWithIdentifier") with the same identifier as the one you will create if it can't find an existing cell.
The line creates a static variable (global in that it is shared by all code paths and only initialized once, but local in that you can only access it in this method) to hold the NSString for the identifier. My guess is that this is to ensure that the same pointer is used every time, as comparing pointers is quick and easy, while comparing the contents of strings can take a little bit longer.
For performance as mentioned but also to get help from the compiler in catching spelling errors. There is no checking of your identifier if you use a #""-string literal. The compiler will error out you if you misspell the static identifier. Also codesense will autocomplete the static identifier.
The identifier is a key or tag that allows you to have multiple separate collections of cells for different purposes.
This saves you time and RAM memory - let's find out how.
Let's suppose you had a contacts list application, with two types of contacts, businesses and friends.
If you wanted to display these differently, then you might design two types of cell - one with a picture (friend photo) and name in black font, and one with just the name of the company and no picture or icon.
When the user is using the application, it might need to display 3 friends and 4 companies with names starting with "A-M" at first, so it needs 3 friend cells and 4 company cells. You pass it these, and tag all the friend cells with the identifier "friend", and all the business ones with the identifier "business".
When later on the view changes and just wants names starting with "P-T", you might just have 7 businesses. Ideally you would re-use the cells you already created, so it requests 7 cells with identifier "business", and it turns out you already tagged 4 cells that you already created with "business", so it simply re-uses those. The remaining 3 you already created have the wrong tag, so it ignores those (or maybe deletes them?) and creates 3 new business type cells and gives them the tag "business".
By re-using cells in this way you save on Memory (only need as many cells can be displayed at once of each type), and Performance (no need to go to the effort of allocating and initialising new cells while scrolling up and down). You trade this off against the additional programmer effort of writing this selection code and giving things ids.
They could have automatically tagged cells based upon the objective-C type, but this wouldn't work if you programmatically created the contents of a cell rather than subclassing or using the Interface builder to lay out your cells. So they provide the identifier mechanism instead.
If you only have one type of cell in your table, just call it "Alice" and forget about it.