Search code examples
iphoneobjective-cuitableviewibaction

Need to take Action on UIButton press that is in nested TableViews with custom UITableViewCell


I have a UITableViewController (called Details). In that tableView on row 1 I have another UITableView which I rotate horizontally. Each cell in that tableview is a custom UITableViewCell (called DetailsHorizontalPhotoCell) which displays a button with an image. I am having a hard time taking action when the photos (the buttons) are pressed.

Calling an IBAction is not a problem for me if I have that IBAction code in the DetailsHorizontalPhotoCell.h/.m files however I need to present a modal VC upon the button presses. That code does not belong and/or work in a tableViewCell - it needs to be in the controller (Details). That being said I can't figure out how to code it. This is what I have so far.

Details.m:

if( (indexPath.row == 1) && ([detailsObject_.photo count] > 0) )
{
    NSLog(@"** In Photo Section **");

    static NSString *CellIdentifier = @"cellPhoto";
    DetailsHorizontalPhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil)
    {
        cell = [[DetailsHorizontalPhotoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    CGAffineTransform rotateTable = CGAffineTransformMakeRotation(-M_PI_2);
    cell.horizontalTableView.transform = rotateTable;

    NSArray *photoArray = [[NSArray alloc] initWithArray:detailsObject_.photo];

    cell.horizontalTableView.frame = CGRectMake(0, 0, cell.horizontalTableView.frame.size.width, cell.horizontalTableView.frame.size.height); 
    cell.contentArray = [NSArray arrayWithArray:photoArray];
    cell.horizontalTableView.allowsSelection = YES;

    return cell;
}

DetailsHorizontalPhotoCell.m:

@synthesize horizontalTableView = horizontalTableView_;
@synthesize contentArray = contentArray_;
@synthesize imageButton = imageButton_;

// *** a bunch of code which is not relevant to this question snipped out here... 


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [self.horizontalTableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
    }

    for(UIButton *button in cell.subviews)
    {
        [button removeFromSuperview];
    }

    // create image and button
    UIImage *image = [UIImage imageNamed:[contentArray_ objectAtIndex:indexPath.row]];
    self.imageButton = [[UIButton alloc] initWithFrame:CGRectMake(5, 5, 240, 240)];

    // setup the button
    [imageButton_ setImage:image forState:UIControlStateNormal];
    imageButton_.layer.masksToBounds = YES;
    imageButton_.layer.cornerRadius = 5.0;
    imageButton_.layer.borderWidth = 1.0;
    imageButton_.layer.borderColor = [[UIColor grayColor] CGColor];

    // rotate the button
    CGAffineTransform rotateButton = CGAffineTransformMakeRotation(M_PI_2);
    imageButton_.transform = rotateButton;

    // this detects the click of each photo and triggers the IBAction
    [imageButton_ addTarget:self action:@selector(photoButton:) forControlEvents:UIControlEventTouchUpInside];

    /// do more stuff yada yada yada... <snipped code> and return cell;
}


// the Action (which I know should NOT be in this UITableViewCell class)
- (IBAction)photoButton:(id)sender 
{
    CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.horizontalTableView];
    NSIndexPath *hitIndex = [self.horizontalTableView indexPathForRowAtPoint:hitPoint];

    NSLog(@"Image clicked, index: %d", hitIndex.row);

    // presentModalViewController here - but you can't do that from here! Must be in the Details controller
}

So, knowing that I can't perform a presentModalViewController from the Cell I try to move that code into the Details class (below)

Add the IBAction to Details.h and .m

- (IBAction)photoButton:(id)sender 
{
    DetailsHorizontalPhotoCell *cell = [[DetailsHorizontalPhotoCell alloc] init];

    CGPoint hitPoint = [sender convertPoint:CGPointZero toView:cell.horizontalTableView];
    NSIndexPath *hitIndex = [cell.horizontalTableView indexPathForRowAtPoint:hitPoint];

    NSLog(@"Image clicked, index: %d", hitIndex.row);
}

And add the click event to the photo button in the Details cellForRowAtIndexPath method.

// Blaaahh! Tried a million combinations of something like below but cannot get it to work... 
//[cell.imageButton addTarget:cell.horizontalTableView action:@selector(photoButton:) forControlEvents:UIControlEventTouchUpInside];

PS - I'm using IOS5, Xcode 4.2 w/ ARC


Solution

  • First of all there is no need to add the button each time a cell is displayed. You only have to add the subviews of the cell when you create a new cell. You'll get better scrolling performance this way.

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
    
        UITableViewCell *cell = [self.horizontalTableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if(cell == nil)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
            [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
    
            // Add image button to cell
            UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(5, 5, 240, 240)];
            // set tag so you can get the button later
            button.tag = 1021;
            [button addTarget:self action:@selector(photoButton:) forControlEvents:UIControlEventTouchUpInside]
            // more button config
            [cell.contentView addSubView:button];
        }
        // get image button
        UIButton *button = [cell.contentView viewWithTag:1021];
        // configure image button
        UIImage *image = [UIImage imageNamed:[contentArray_ objectAtIndex:indexPath.row]];
        [button setImage:image forState:UIControlStateNormal];
        return cell;
    }
    

    Next you can use use superview on the button (= sender) to go back to the UITableViewCell. With the cell you can get the indexPath of that cell.

    - (IBAction)photoButton:(id)sender 
    {
        UIView *contentView = [sender superview];
        UITableViewCell *cell = [contentView superview];
        NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    }