UIButton in UITableView cell like "Delete Event"

22,832

Solution 1

The way you have it now each time a cell is shown you're allocating a button, setting its value, and adding it to the cell's contentView. When the cell gets reused (via dequeueReusableCellWithIdentifier) you'll be creating another new button, adding it to the cell (on top of the old one) etc. The fact that it's gone through addSubview but no explicit release means each button's retain count will never go to zero so they'll all stick around. After a while of scrolling up and down the cell will end up with hundreds of button subviews which probably isn't what you want.

A few tips:

  • Never allocate stuff inside a cellForRowAtIndexPath call unless it's done when dequeueReusableCellWithIdentifier is returning nil and you're initializing the cell. All other subsequent times you'll be handed back the cached cell that you will have already set up so all you have to do is change the labels or icons. You're going to want to move all that button allocation stuff up inside the if conditional right after the cell allocation code.

  • The button needs to have a position and also a target set for it so it'll do something when tapped. If every cell is going to have this button a neat trick is to have them all point to the same target method but set the button's tag value to the indexPath.row of the cell (outside the cell allocation block since it varies for each cell). The common tap handler for the button would use the tag value of the sender to look up the underlying data in the dataSource list.

  • Call release on the button after you've done an addSubview. That way the retain count will fall to zero and the object will actually get released when the parent is released.

  • Instead of adding the button via addSubview, you can return it as the accessoryView for the cell so you don't have to worry about positioning it (unless you're already using the accessoryView for something else -- like disclosure buttons).

Solution 2

I took a different approach to creating an equivalent to the 'Delete Event' button in the Calendar app. Rather than add a button as a subview, I added two background views (red and darker red, with nice gradients) to the cells and then rounded off the corners and set the border to grey.

The code below creates a re-usable cell (in the usual fashion). The two images referred to ('redUp.png' and 'redDown.png') were taken from a screenshot of the calendar's 'Delete Event' button. (That seemed quicker than creating the gradients programmatically.) There's scope for a bit more fine tuning to get it even closer to the Calendar's 'Delete Event' appearance, but this is pretty close.

The button's action is triggered by the tableView delegate method tableView:didSelectRowAtIndexPath: method.

// create a button from a table row like the Calendar's 'Delete Event' button
// remember to have an #import <QuartzCore/QuartzCore.h> some above this code

static NSString *CellWithButtonIdentifier = @"CellWithButton";

 UITableViewCell *cell = [self dequeueReusableCellWithIdentifier:CellWithButtonIdentifier];
 if (cell == nil) {
  cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellWithButtonIdentifier] autorelease];
  [[cell textLabel] setTextAlignment: UITextAlignmentCenter];

  UIImageView* upImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"redUp.png"]];
  UIImageView* downImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"redDown.png"]];

  [cell setBackgroundView: upImage];
  [cell setSelectedBackgroundView: downImage];

  [[upImage layer] setCornerRadius:8.0f];
  [[upImage layer] setMasksToBounds:YES];
  [[upImage layer] setBorderWidth:1.0f];
  [[upImage layer] setBorderColor: [[UIColor grayColor] CGColor]];

  [[downImage layer] setCornerRadius:8.0f];
  [[downImage layer] setMasksToBounds:YES];
  [[downImage layer] setBorderWidth:1.0f];
  [[downImage layer] setBorderColor: [[UIColor grayColor] CGColor]];

  [[cell textLabel] setTextColor: [UIColor whiteColor]];
  [[cell textLabel] setBackgroundColor:[UIColor clearColor]];
  [cell  setBackgroundColor:[UIColor clearColor]]; // needed for 3.2 (not needed for later iOS versions)
  [[cell textLabel] setFont:[UIFont boldSystemFontOfSize:20.0]];

  [upImage release];
  [downImage release];
 }
 return cell;

Solution 3

Yes, you are on the right track, that's the easiest way to add a subview to a cell (the other is subclassing a UITableViewCell).

Check the Apple guide for more info.

Solution 4

Yes, this is generally the correct approach.

A tip:

  • Set the callback for your button events, so that it actually does something when clicked.

    [myButton addTarget:self action:@selector(whatMyButtonShouldDo:)   
      forControlEvents:UIControlEventTouchUpInside];
    

EDIT: Adjusted code for using a system button, which renders a lot of what I had written uneccessary.

Share:
22,832
Marcel Stör
Author by

Marcel Stör

Passionate clean coder, leading by example, from Switzerland. created first web site in 1997 been working with Java/Kotlin and web technologies since 1999 very much into IoT as well these days collaborator with the NodeMCU team on GitHub running nodemcu-build.com providing Docker NodeMCU build image providing NodeMCU PyFlasher to flash binaries to ESP8266, ESP8285 and ESP32 Co-founder of

Updated on January 21, 2020

Comments

  • Marcel Stör
    Marcel Stör over 4 years

    I'd like to add a button to a table cell. The "Delete Event" in the calendar app inspired me... (a similar case is "Share Contact" in contacts)

    As of now there's

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
      //..yadayadayada
      cell = [tableView dequeueReusableCellWithIdentifier:@"buttonCell"];
      if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"buttonCell"] autorelease];
      }
      UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoDark];
      [button setBackgroundColor:[UIColor redColor]];
      button.titleLabel.text = @"Foo Bar";
      [cell.contentView addSubview:button];
    

    which produces a button, indeed. It doesn't look yet how it's supposed to (it's obvious I've never dealt with buttons in iPhone, yet), but is this at least the right approach?

  • Daniel Rinser
    Daniel Rinser almost 15 years
    In his example code, there is no need to release the button, because [UIButton buttonWithType:] returns an autoreleased instance.
  • mmc
    mmc almost 15 years
    You're exactly correct, I copied that from code I had where I used my initWithFrame. Adjusting.
  • Marcel Stör
    Marcel Stör almost 15 years
    Thanks for the feedback. The hint with release/autorelease was noted. Is it really necessary to fiddle with the frame? I simply want the button to fill the entire table cell (i.e not worry about position and size); "Delete Event" demonstrates this nicely.
  • mmc
    mmc almost 15 years
    Yeah, I made things too complicated by copying my code, where I don't use System buttons, I set the frame and the background images by hand. I abbreviated my answer considerably.
  • Marcel Stör
    Marcel Stör almost 15 years
    Thanks a bunch for the valuable hints and explanations. An iPhone programming newbie like me sure appreciates that. As for tip 4, as with "Delete Event"/"Share Contact" I'll only have one button at the bottom of the UI i.e. in the last row. So, it cannot be the accessoryView, but it has to fill the entire row. I'm still struggling with that.
  • DougW
    DougW over 14 years
    Regarding #3, releasing the button will crash your application. He isn't allocing it.
  • Greg
    Greg about 12 years
    You should add [cell.textLabel setShadowColor:[UIColor blackColor]]; [cell.textLabel setShadowOffset:CGSizeMake(0, -1)]; to the code. That gives the text a slightly embossed effect so it looks more like the native button.