views:

870

answers:

2

Hi,

I have a UITableView with two sections: free and paid. Each section has a different kind of cell. The free cells have two labels and an image. The paid cells has two labels, an image and a button that allows the product to be bought. Once a product is bought, the BUY button on that particular cell must not be shown again. Said that, this is how the cells are initialized...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *IDNormal = @"Normal";
    static NSString *IDComplex = @"Complex";


    if (indexPath.section == 0) { // show free objects 

     UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:IDNormal];

     if (cell == nil) {
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                reuseIdentifier:IDNormal] autorelease];

      // product description
      UILabel * labelDescription = [[UILabel alloc] initWithFrame:CGRectMake(5.0, 54.0, 225, 18)];
      [labelDescription setTextAlignment:UITextAlignmentLeft];
      [labelDescription setBackgroundColor:[UIColor whiteColor ]];
      [labelDescription setClipsToBounds:YES];
      [labelDescription setFont:[UIFont systemFontOfSize:14.0]];
      [labelDescription setTextColor:[UIColor blackColor]];
      [labelDescription setAlpha:0.6];
      [labelDescription setTag: 860];
      [cell addSubview:labelDescription];
      [labelDescription release];

      // this will show the word FREE on free objects (cells)
      UILabel * labelFREE = [[UILabel alloc] initWithFrame:CGRectMake(235.0, 54.0, 80, 18)];
      [labelFREE setTextAlignment:UITextAlignmentCenter];
      [labelFREE setBackgroundColor:[UIColor greenColor ]];
      [labelFREE setClipsToBounds:YES];
      [labelFREE setFont:[UIFont boldSystemFontOfSize:14.0]];
      [labelFREE setTextColor:[UIColor blackColor]];
      [labelFREE setAlpha:0.75];
      [labelFREE setText:NSLocalizedString(@"freeKey", @"")];  
      [labelFREE setTag: 861];
      [cell addSubview:labelFREE];
      [labelFREE release];

     }


     cell.imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] 
      pathForResource:[NSString stringWithFormat: @"free%d", indexPath.row] ofType:@"jpg"]];  
     NSString * prefixLabel = [NSString stringWithFormat: @"gratis%d", indexPath.row];

     UILabel *labelDescription2 = (UILabel*)[cell viewWithTag:860];
     [labelDescription2 setText:@"FREE"];

     return cell;

    } else { // show paid objects

     UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:IDComplex];

     if (cell == nil) {
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                reuseIdentifier:IDComplex] autorelease];

      UILabel * labelDescription = [[UILabel alloc] initWithFrame:CGRectMake(5.0, 54.0, 225, 18)];
      [labelDescription setTextAlignment:UITextAlignmentLeft];
      [labelDescription setBackgroundColor:[UIColor whiteColor ]];
      [labelDescription setClipsToBounds:YES];
      [labelDescription setFont:[UIFont systemFontOfSize:14.0]];
      [labelDescription setTextColor:[UIColor blackColor]];
      [labelDescription setAlpha:0.6];
      [labelDescription setTag: 1];
      [cell addSubview:labelDescription];
      [labelDescription release];

     }



     int numberPaidObject = indexPath.row + 500;



     cell.imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] 
      pathForResource:[NSString stringWithFormat: @"table-pg%d", numberPaidObject] ofType:@"jpg"]];  

     NSString * nomeDoProduto = [NSString stringWithFormat: @"paid%d", numberPaidObject];


     if ( NotSoldProduct ) {

      NSString * prefixoEtiqueta = [NSString stringWithFormat: @"paid%d", numberPaidObject];

      UILabel *labelDescription2 = (UILabel*)[cell viewWithTag:1];
      [labelDescription2 setText:[description objectAtIndex: numberPaidObject ];

     }

     return cell;

    }
}

as I have to identify which BUY button was clicked and I am using dequeueReusableCellWithIdentifier, so I have to use the following method...

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

    int numeroOfPaidObject = indexPath.row + 500;

    if ((indexPath.section == 1) && ObjectForSale) {
     // if paid objects and object was not bought yet
     // in theory this section will not be executed if the object was already bought and paid
     // so, I am skipping the BUY button creation and, in theory the cell will not have a BUY
     // button… the problem is that it does... 

     UIButton * buyButton = [[UIButton alloc] initWithFrame:CGRectMake( 235, 45, 80, 30)];
     buyButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
     buyButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
     [buyButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
     buyButton.titleLabel.font = [UIFont boldSystemFontOfSize:14];
     buyButton.backgroundColor = [UIColor clearColor];
     [buyButton addTarget:self action:@selector(buyNow:) forControlEvents:UIControlEventTouchDown];

     [buyButton setTitle:NSLocalizedString(@"buyKey", @"") forState:UIControlStateNormal]; 

     UIImage *newImage = [[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] 
              pathForResource: @"whiteButton" ofType:@"png"]]
           stretchableImageWithLeftCapWidth:12.0f topCapHeight:0.0f];

     [buyButton setBackgroundImage:newImage forState:UIControlStateNormal];

     [buyButton setTag: numeroOfPaidObject];
     [cell addSubview:buyButton];

     [buyButton release];
    }
}

The problem with this code is: when I reload table's data the BUY button continues to show up on all cells, including those that was already bought by the user.

Any ideas?

thanks for any help.

+6  A: 

You can step through it with the debugger, but here is my guess:

Because table cells are being reused, eventually all of the already purchased items end up in table cells that were previously occupied by an unpurchased item and therefore have had a buy button added to them.

If this is the case you need to add an else clause in your second section of code that deletes the button from the cell if the product has already been purchased.

Another problem is that your code is adding a new button every time a cell is displayed, so many/most of your cells probably have several buttons in them.

You will want to refactor your code so that the button is added once, in tableView:cellForRowAtIndexPath:, and only for newly-created cells.

There are two ways to do that:

  1. Split the "Complex" cell type into two types: purchased and unpurchased, and create/dequeue them separately. Add the button only to to the unpurchased cell type.

  2. Add the buttons to all of the cells in the "paid" section, and hide it when the item has been purchased.

In either case you'll need a way to grab a pointer to the button. The preferred way to do this is with the tag property, making it a one liner:

UIButton * button = [cell viewForTag:42];
...

You can also iterate through the subviews of the cell looking for UIButton objects:

UIButton *button = nil;

foreach (UIView *view in cell.subviews) {
    if ([view isKindOfClass:[UIButton class]])
        button = (UIButton *)view;
}

I would suggest using a constant tag to identify the button, and then rewrite your buyNow: method as follows:

-(IBAction)buyNow:(id)sender {
    UITableViewCell *cell = (UITableViewCell *)((UIView *)sender).superview;

    int itemID = [tableView indexPathForCell:cell].row + 500;

    ...
}
Frank Schmitt
Digital Robot
The problem is that the button doesn't go away when the cell is recycled. Once it has been added to the cell's view hierarchy, it will stay there until you explicitly remove it. In fact I'm willing to bet that many of your cells have several buttons stacked on top of one another in them.
Frank Schmitt
I've had situations where I've set the color of text to red in an `if` clause, and had red text in cells where the code in the `if` was never reached. After adding an 'else cell.textLabel.color = [UIColor blackColor]` line everything worked as expected.
Frank Schmitt
I thought when a cell was recycled the contents were released. All labels and buttons I have added to these sells were autorelease. So, how can I really release everything inside a cell before drawing it, of in other words, how to add stuff to tables so I do not end with stacked buttons, labels, etc. Please add some code to your comment.
Digital Robot
The cell will `retain` the views you add to it. A third solution would be to never dequeue a cell but always create one from scratch. But the performance would suck :(.
Frank Schmitt
thanks. I cannot add the button once on tableView:cellForRowAtIndexPath:, because if I do, the button tag will be cached and I will have just seven different tags. These tags are used to identify which button was clicked. I have tried your solution #2 and it does not work too after a reloadData, as the table is showing the cell with the button and on the queue, the button is not hidden. Your solution #1 will probably work but the problem of stacking objects persists. According to you, and I suppose you are right, there are lots of stacked objects on each cell, so, how can I get rid of them?
Digital Robot
Sorry, I didn't notice that you were already using the tag to ID the object. I'll update my answer.
Frank Schmitt
That's it! This solution of coming with the fixed tag and then create the real tag in the buyNow method is perfect. Thanks!!!
Digital Robot
A: 

Perhaps I am thinking too simply here but a table view will function just fine with only the delegates set, but will fail to respond to reloadData if you have not made the connection in IB, did you check that?

Adam Eberbach