Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Sign In with Google Sign In with OpenID

Reusing cells for increased performance + adding subviews to cells

ambiensignalambiensignal Posts: 145Registered Users
edited March 2012 in iPhone SDK Development
Hi,
I'm trying to add a searchbar subview to only the first cell of the table. It works fine, but it seems to keep popping up on cells that I don't want it to be in if I scroll down further, because I'm doing if (cell == nil) { before I set cell. If I don't do this though, performance is severely cut down. What can I do?

Thanks for any help.
Post edited by ambiensignal on

Replies

  • dough boydough boy Posts: 25Registered Users
    edited February 2009
    It sounds like you are using "dequeueReusableCellWithIdentifier".

    I had this same problem and basically what is happening is that cells are reused every 7 or 8 or so. I got around it by setting the identifier as a unique id so:
    NSString *CellIdentifier = [NSString stringWithFormat:@"%@ %d", @"DataCell_", indexPath.row];
    

    This will prevent the cells from being reused so only your first cell will contain what you want.

    Hope that helps.
    <a href="http://comicbookrealm.com" target="_blank"><b>ComicBookRealm.com</b></a> - We all spend too much on comics...why pay to keep track of them?
  • RickMaddyRickMaddy Posts: 2,122New Users
    edited February 2009
    Sorry but DO NOT do what 'dough boy' is recommending. What he recommends is create a unique cell for every row regardless of how many rows there are. This goes against the basic UITableView design. And it wastes a lot of memory.

    You are supposed to reuse cells. Here's the guidelines I use.

    1) Use the built in 'reuse' logic as shown in all of Apple's table based example apps.
    2) Use a different cell identifier for each type of cell.
    3) Make sure you set all attributes of the reused cell so you don't see data from other cells appearing in a cell.

    So my general template for 'cellForRowAtIndexPath' looks like this:
    - (void)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        if (<indexPath is for cell type "A">) {
    	static NSString *SomeIdentifierA = @"SomeIdentifierA";
    
            // This could be some custom table cell class if appropriate	
    	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SomeIdentifierA];
    	if (cell == nil) {
                cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:SomeIdentifierA] autorelease];
                // Any other attributes of the cell that will never change for type "A" cells.
    	}
    
            if (someCondition) {
                cell.textColor = <someColor>;
            } else {
                cell.textColor = <anotherColor>;
            }
            cell.text = <some text>;	
    	return cell;
        } else if (<indexPath is for cell type "B") {
    	static NSString *SomeIdentifierB = @"SomeIdentifierB";
    	
            // This could be some custom table cell class if appropriate	
    	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SomeIdentifierB];
    	if (cell == nil) {
                cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:SomeIdentifierB] autorelease];
                // Any other attributes of the cell that will never change for type "B" cells.
    	}
    
            cell.text = <some text>;	
    	return cell;
        } else {
            return nil; // Oops - should never get here
        }
    }
    

    I have some code where I can have 5 different types of table cells so this method starts to get longer but the same pattern applies. Most people only have one type of cell in a table, occasionally two.

    For any given cell type you must set a given set of cell attributes for all conditions. For example if you set the cell text color differently under some conditions you must set the appropriate color for ALL conditions. Otherwise reused cells may end up with the wrong text color.
  • dough boydough boy Posts: 25Registered Users
    edited February 2009
    Ok...just for me to know down the road, why should you reuse cells? My app is pulling in images from the web and when you go up or down it would reuse the cell, but it would then reload the image every time.

    I guess I could get around it by caching the image on the device, but no matter what I did it would regenerate the cell and that seemed like a waste.
    <a href="http://comicbookrealm.com" target="_blank"><b>ComicBookRealm.com</b></a> - We all spend too much on comics...why pay to keep track of them?
  • RickMaddyRickMaddy Posts: 2,122New Users
    edited February 2009
    Imagine your table has 1000 rows. Your approach would keep all 1000 rows in memory if the user scrolled through the whole table. The standard reuse approach means that no more than 8 or so cells would ever be in memory.

    Your case is a little more complex due to the content of each of your cells. Obviously you don't want to download the same images over and over as the user scrolls around so some sort of image caching would be a good idea.

    Look at all of the table based example apps. None of them do what you are doing. They all use the standard reuse functionality.
  • dough boydough boy Posts: 25Registered Users
    edited February 2009
    Yeah...and the feed is controlled so we know that we will only have 20 items in it at a time.

    Of course if I show all 20 and the user scrolls the app eventually crashes with a exc_bad_access message. If I limit it to 10 the user can scroll all day long with no problems. I have another question about how to figure out what the problem would be in this case.

    It is strange with all 20 being rendered and no problems (you can see with a quick scroll). But if the user goes up or down, it dies.
    <a href="http://comicbookrealm.com" target="_blank"><b>ComicBookRealm.com</b></a> - We all spend too much on comics...why pay to keep track of them?
  • CaptainCodeCaptainCode Posts: 91Registered Users
    edited February 2009
    You're accessing an invalid memory address when you get exec_bad_access. The table view seems to throw away some cells sometimes anyways since if you scroll down your cellForRowAtIndexPath gets called when scrolling back up(but not always).
  • dough boydough boy Posts: 25Registered Users
    edited February 2009
    So is it possible to not use the "dequeueReusableCellWithIdentifier"?

    What would I need to change below so I don't have a memory access error?
    ArticleListCell *cell = (ArticleListCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    <a href="http://comicbookrealm.com" target="_blank"><b>ComicBookRealm.com</b></a> - We all spend too much on comics...why pay to keep track of them?
  • ambiensignalambiensignal Posts: 145Registered Users
    edited February 2009
    RickMaddy wrote: »
    Sorry but DO NOT do what 'dough boy' is recommending. What he recommends is create a unique cell for every row regardless of how many rows there are. This goes against the basic UITableView design. And it wastes a lot of memory. [/QUOTE] Wow, this is [i]extremely[/i] useful, especially with the comments. Thanks a lot, Rick.[code follows...]

    Wow, this is extremely useful, especially with the comments. Thanks a lot, Rick.
  • ThelordofringThelordofring Posts: 59Registered Users
    edited February 2009
    hi,
    i have read the above discussion and i have the same problem that i am am drawing some drawing in the table cell view and when i use reusable cell the application will crash after some ruff navigation over scroll and when i did'nt use reusable cell it is working fine
    what will i do to remove crash
  • RickMaddyRickMaddy Posts: 2,122New Users
    edited February 2009
    How do you expect anyone to be able to help you when you provide NO useful information about what's wrong? You might as well post the following:

    "My app doesn't work. Why?"

    When your app crashes there must be messages in the console. There is a stack trace in the debugger. Use this information to track down what line of code is causing the problem. Make sure you are properly retaining and release objects.

    If you are still stuck after analyzing all of the useful information given to you when the app crashes then you can post a question here but help us help you. Post the actual error message. Post some relevant code and add comments about where the crash is actually occurring.
  • MithunMadhavMithunMadhav Posts: 3New Users
    edited September 2010
    This method you explained is rely cool.And suddenly the UITableView and its cells make sense to me!!!Thanks a million
  • spiderguy84spiderguy84 Posts: 994Registered Users @ @ @
    edited March 2012
    This method you explained is rely cool.And suddenly the UITableView and its cells make sense to me!!!Thanks a million

    I know it has been quite a while, but I am having similar issues. Trying to add progressView to whichever cell is selected, and it is adding it to every 8 cells. Here is my cellforrow
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        static NSString *CellIdentifier = @"Cell";
        
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
            
        }
        
    
    
        RSSEntry *entry = [_allEntries objectAtIndex:indexPath.row];
    
        NSDateFormatter * dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
        [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
        [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
        NSString *articleDateString = [dateFormatter stringFromDate:entry.articleDate];
        cell.textLabel.text = entry.articleTitle; 
        cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@", articleDateString, entry.blogTitle];
       
    	UIFont *cellFont = [UIFont fontWithName:@"MarkerFelt-Thin" size:17];    
        cell.textLabel.font = cellFont;
        UIFont *cellFont2 = [UIFont fontWithName:@"MarkerFelt-Thin" size:12];    
        cell.detailTextLabel.font = cellFont2;
        return cell;
    }
    
    I set the progressview in didSelectRowAtIndex, thinking it would keep this from happening:
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
             CGRect frame = CGRectMake(0, 49, 160, 50);
             progress = [[UIProgressView alloc] initWithFrame:frame];
             cell.contentView.tag = 100;
             [cell.contentView addSubview:progress];
                 RSSEntry *entry = [_allEntries objectAtIndex:indexPath.row];
                 
                 self.nameit = entry.articleTitle;
             NSURL *url = [NSURL URLWithString:entry.articleUrl];    
             NSURLRequest *theRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
             receivedData = [[NSMutableData alloc] initWithLength:0];
             NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:YES];
             self.thetable = tableView;
             self.thepath = indexPath;
    
    Then in the NSURLConnection methods I either hide or show the progress bar. How can I properly set this up? I understand the concept of it reusing everything, and I understand how to set a tag on the progressview, but not sure how to add it using the tag.
    <a href="http://itunes.apple.com/us/app/i-miss-mommy/id522287292?mt=8" target="_blank">My latest app...i Miss Mommy</a>
  • RLScottRLScott Posts: 1,638Tutorial Authors, Registered Users @ @ @ @
    edited March 2012
    Here is the problem with what you are doing. When you add a progress view to a cell the progress view stays with the cell for it's entire lifetime (unless the progress view is explicitly removed as a subview). Instead of worrying about which cells have a progress view and dynamically adding them in or removing them, just give every cell a progress view when it is first created in the block with if(cell == nil). But then hide the progress view immediately. So every cell has a hidden progress view. Then, when a cell is selected, unhide the progress view for that cell only. You will also have to make sure the progress view gets re-hidden whenever a cell becomes unselected. If you give the progress view a known tag value then you can find the progress view associated with each cell for this hiding and unhiding. There is a function (I can't remember what it is called now) to find a subview of a view given a certain tag. And you will have to figure out how to know when a cell is being unselected. One way to do that would be to keep a reference to the last selected cell. Then when any cell is selected, first hide the progress view of the last selected cell.

    I just realized a major problem with this approach. If a selected cell gets scrolled out of view it still has a non-hidden progress view as it goes into the queue. Then when it comes out of the queue it may be assigned to a different index in the table - one that should no longer be showing a progress view. So you will have to reevaluate the hidden status as each cell is assigned from the queue.
  • spiderguy84spiderguy84 Posts: 994Registered Users @ @ @
    edited March 2012
    RLScott wrote: »
    Here is the problem with what you are doing. When you add a progress view to a cell the progress view stays with the cell for it's entire lifetime (unless the progress view is explicitly removed as a subview). Instead of worrying about which cells have a progress view and dynamically adding them in or removing them, just give every cell a progress view when it is first created in the block with if(cell == nil). But then hide the progress view immediately. So every cell has a hidden progress view. Then, when a cell is selected, unhide the progress view for that cell only. You will also have to make sure the progress view gets re-hidden whenever a cell becomes unselected. If you give the progress view a known tag value then you can find the progress view associated with each cell for this hiding and unhiding. There is a function (I can't remember what it is called now) to find a subview of a view given a certain tag. And you will have to figure out how to know when a cell is being unselected. One way to do that would be to keep a reference to the last selected cell. Then when any cell is selected, first hide the progress view of the last selected cell.

    I just realized a major problem with this approach. If a selected cell gets scrolled out of view it still has a non-hidden progress view as it goes into the queue. Then when it comes out of the queue it may be assigned to a different index in the table - one that should no longer be showing a progress view. So you will have to reevaluate the hidden status as each cell is assigned from the queue.

    I'm starting to think that since I'm only doing one download at a time that I may just have the progressview in the bar button item area and hide it when not being used.
    <a href="http://itunes.apple.com/us/app/i-miss-mommy/id522287292?mt=8" target="_blank">My latest app...i Miss Mommy</a>
  • RLScottRLScott Posts: 1,638Tutorial Authors, Registered Users @ @ @ @
    edited March 2012
    David54 wrote: »
    Sorry but DO NOT do what 'dough boy' is recommending. What he recommends is create a unique cell for every row regardless of how many rows there are. This goes against the basic UITableView design. And it wastes a lot of memory.
    I can think of at least one instance where it is very useful to create a unique cell for every row. Suppose that you had a limited number of rows (say about 80) where every row has a UITextField in it where the user can enter data, jumping from cell to cell at will. If you create unique cells for every row and totally forget about the reuse mechanism then it is easy to let every UITextView maintain its own content. But if you insist on using the reuse mechanism then you would have to capture the content of every cell as it loss focus and store that content in an array of strings and restore that content whenever a row comes back into view. If you know that the number of rows cannot grow beyond a reasonable limit, then the extra memory is irrelevant. It will all be reclaimed anyway as soon as the table view is destroyed.

    But by and large, the reuse mechanism is the way to go.
Sign In or Register to comment.