UITableViewCell expand on click

IphoneObjective CUitableviewUibutton

Iphone Problem Overview


Lets say we have a custom UITableViewCell

So whenever I click custom button on cell.. it should expand to the some extent (you can say 40 height more...) and when i click again to the same custom button it should collapse to the previous height.

Developer's please guide me.. how can I achieve this task

Iphone Solutions


Solution 1 - Iphone

I'm not going to say anything here to contradict the accepted answer considering it is perfectly correct. However, I am going to go into more detail on how to accomplish this. If you don't want to read through all this and are more interested in playing with the source code in a working project, I've uploaded an example project to GitHub.

The basic idea is is to have a condition inside of the method -tableView: heightForRowAtIndexPath: that determines whether or not the current cell should be expanded. This will be triggered by calling begin/end updates on the table from within -tableView: didSelectRowAtIndexPath: In this example, I'll show how to make a table view that allows for one cell to be expanded at a time.

The first thing that you'll need to do is declare a reference to an NSIndexPath object. You can do this however you want, but I recommend using a property declaration like this:

@property (strong, nonatomic) NSIndexPath *expandedIndexPath;

NOTE: You do not need to create this index path inside viewDidLoad, or any other similar method. The fact that the index is initially nil will only mean that the table will not initially have an expanded row. If you would rather the table start off with a row of your choice expanded, you could add something like this to your viewDidLoad method:

NSInteger row = 1;
NSInteger section = 2;
self.expandedIndexPath = [NSIndexPath indexPathForRow:row inSection:section];

The next step is to head on over to your UITableViewDelegate method -tableView: didSelectRowAtIndexPath: to add the logic to alter the expanded cell index based on the users selection. The idea here is to check the index path that has just been selected against the index path stored inside the expandedIndexPath variable. If the two are a match, then we know that the user is trying to deselect the expanded cell in which case, we set the variable to nil. Otherwise, we set the expandedIndexPath variable to the index that was just selected. This is all done between calls to beginUpdates/endUpdates, to allow the table view to automatically handle the transition animation.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView beginUpdates]; // tell the table you're about to start making changes

    // If the index path of the currently expanded cell is the same as the index that
    // has just been tapped set the expanded index to nil so that there aren't any
    // expanded cells, otherwise, set the expanded index to the index that has just
    // been selected.
    if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
        self.expandedIndexPath = nil;
    } else {
        self.expandedIndexPath = indexPath;
    }

    [tableView endUpdates]; // tell the table you're done making your changes
}

Then the final step is in another UITableViewDelegate method -tableView: heightForRowAtIndexPath:. This method will be called after you've triggered beginUpdates once for each index path that the table determines needs updating. This is where you'll compare the expandedIndexPath against the index path that is currently being reevaluated.

If the two index paths are the same, then this is the cell that you wish to be expanded, otherwise it's height should be normal. I used the values 100 and 44, but you can use what ever suits your needs.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Compares the index path for the current cell to the index path stored in the expanded
    // index path variable. If the two match, return a height of 100 points, otherwise return
    // a height of 44 points.
    if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
        return 100.0; // Expanded height
    }
    return 44.0; // Normal height
}

Solution 2 - Iphone

Implement heightForRowAtIndexPath to calculate the right height. Then in the code for your button, force the table to reevaluate each cell's height with beginUpdates plus endUpdates:

[self.tableView beginUpdates];
[self.tableView endUpdates];

Changes to the tableview cells' heights will automatically be calculated with heightForRowAtIndexPath and the changes will be animated too.

In fact, instead of a button on your cell that does this, you might even just make selecting the cell do this in didSelectRowAtIndexPath.

Solution 3 - Iphone

Instead of using [tableView beginUpdates] and [tableView endUpdates] , I'm using the [tableView reloadRowsAtIndexPath:... withRowAnimation:...] method inside the didSelectRowAtIndexPath method.

I prefer this, because I had some problems with elements that should show, when I expand my UITableViewCell, when I used the begin & end updates methods. Another point is that you can choose between some animations like: Top, Bottom, Left, Right...

Solution 4 - Iphone

I have created an open source library for this. You just implement collapse and expand delegates in your code and voilà! you can also perform any drawings and animations. check out this.

enter image description here

Solution 5 - Iphone

I've made a reusable component that will do exactly what you're talking about. It's pretty easy to use, and there's a demo project.

GCRetractableSectionController on GitHub.

Solution 6 - Iphone

This is Mick's answer but for Swift 4. (IndexPath replaces NSIndexPath, which comes with an empty IndexPath as nil would crash Swift. Also, you can compare two instances of IndexPath using ==)

Declare the expandedIndexPath property.

var expandedIndexPath = IndexPath()

Optional viewDidLoad part.

expandedIndexPath = IndexPath(row: 1, section: 2)

Then the didSelectRow part.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.beginUpdates()
    
    if indexPath == expandedIndexPath {
        expandedIndexPath = IndexPath()
    } else {
        expandedIndexPath = indexPath
    }
    
    tableView.endUpdates()
}

Then the heightForRow part.

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath == expandedIndexPath {
        return 100
    }
    
    return 44
}

Solution 7 - Iphone

I used Gcamp's source code and made my own version.

  1. In a loadView method initialize a mutable array where you will save expanded or non-expanded states of your sections. It is critical to save expanded statuses in a separate array, that is not destroyed while table view scrolls (for instance if you store it in a headerView it will be redrawn and forget weather it was expanded or not). In my case it is _sectionStatuses array.

    • (void)loadView { // At the beginning all sections are expanded _sectionStates = [NSMutableArray arrayWithCapacity:self.tableView.numberOfSections]; for (int i = 0; i < self.tableView.numberOfSections; i++) { _sectionStates[i] = [NSNumber numberWithBool:YES]; } }
  2. Create a custom headerView for a section with a button for expanding. Delegate an action from a button in your headerView to your TableViewController using delegation pattern. You can find suitable images in Gcamp's source code.

  3. Create an action to remove or add rows. Here _foldersArray is my structure, that contains all the data. My section's headerView - MCExpandableAccountHeaderView knows it's own section number - I transfer it there when I create header views for each section. It is critical to transfer it to this method, since you have to know which section is now expanded or stretched.

    • (void)expandClicked:(MCAccountHeaderView *)sender { MCExpandableAccountHeaderView expandableAccountHeaderView = (MCExpandableAccountHeaderView)sender;

    // Finding a section, where a button was tapped NSInteger section = expandableAccountHeaderView.section;

    // Number of rows, that must be in a section when it is expanded NSUInteger contentCount = [_foldersArray[section - 1][@"folders"] count];

    // Change a saved status of a section BOOL expanded = [_sectionStates[section] boolValue]; expanded = ! expanded; expandableAccountHeaderView.expanded = expanded; _sectionStates[section] = [NSNumber numberWithBool:expanded];

    // Animation in a table [self.tableView beginUpdates];

    NSMutableArray* modifiedIndexPaths = [[NSMutableArray alloc] init]; for (NSUInteger i = 0; i < contentCount; i++) { NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:section]; [modifiedIndexPaths addObject:indexPath]; }

    if (expandableAccountHeaderView.expanded) [self.tableView insertRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade]; else [self.tableView deleteRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];

    [self.tableView endUpdates];

    // Scroll to the top of current expanded section if (expandableAccountHeaderView.expanded) [self.tableView scrollToRowAtIndexPath:INDEX_PATH(0, section) atScrollPosition:UITableViewScrollPositionTop animated:YES]; }

  4. It is also important to return correct number or rows in a section depending on wheather it is expanded or not.

    • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { BOOL expanded = [_sectionStates[section] boolValue];

      return expanded ? [_foldersArray[section - 1][@"folders"] count] : 0;
      }

Solution 8 - Iphone

initialize iSelectedIndex = -1; and declare
UITableView *urTableView;

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

return 10;    //Section count

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

return 3; //row count

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

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

}

[cell.textLabel setText:[NSString stringWithFormat:@"sec:%d,row:%d",indexPath.section,indexPath.row]];

return cell;

}


- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{

// adding a label with the tap gesture to the header in each section

headerLabel = [[UILabel alloc]init]; 

headerLabel.tag = section;

headerLabel.userInteractionEnabled = YES;

headerLabel.backgroundColor = [UIColor greenColor];

headerLabel.text = [NSString stringWithFormat:@"Header No.%d",section];

headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height);

UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(gestureTapped:)];

[headerLabel addGestureRecognizer:tapGesture];

return headerLabel;

}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{

return 50.0; //adjust the height as you need

}

- (void)gestureTapped:(UITapGestureRecognizer *)sender{

UIView *theSuperview = self.view; // whatever view contains 

CGPoint touchPointInSuperview = [sender locationInView:theSuperview];

UIView *touchedView = [theSuperview hitTest:touchPointInSuperview withEvent:nil];

if([touchedView isKindOfClass:[UILabel class]])
{

    if (iSelectedIndex != touchedView.tag) { //if new header is selected , need to expand

        iSelectedIndex = touchedView.tag;

    }else{   // if the header is already expanded , need to collapse

        iSelectedIndex = -1;

    }
    
    [urTableView beginUpdates];

    [urTableView endUpdates];

}

}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

// Show or hide cell

float height = 0.0;

if (indexPath.section == iSelectedIndex) {

    height = 44.0; // Show the cell - adjust the height as you need

}

return height;

}

Solution 9 - Iphone

For me it works to use:

  1. on UITableViewDelegate

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

         print("Did select row: \(indexPath.row).")
         
         tableView.beginUpdates()
         tableView.endUpdates()
     }
    
  2. on selectable/expandable UITableViewCell

    override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated)

        configStyle(selected)
     }
    
  3. Important! tableView.rowHeight is .automatic and UITableViewCell is constraint to enable automatic height calculation, i.e. its height constraint are clearly defined like constraint to top/bottom or height constraints added or label intrinsic content size is used.

Solution 10 - Iphone

To add to 0x7fffffff's answer, I found I needed an extra condition in the if statement within didSelectRowAtIndexPath - thus:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
{

   [tableView beginUpdates];

   if (self.expandedIndexPath && [indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
       self.expandedIndexPath = nil;
   } else {
       self.expandedIndexPath = indexPath;
   }

   [tableView endUpdates];

}

Solution 11 - Iphone

Following this medium article on how to expand the cells based on the tap of a button and setting the numbersOfLine for a specific label, I was able to perform the animation using

tableView.beginUpdates()
tableView.performBatchUpdates({
  cell.description.numberOfLines = !expanded ? 0 : 3
}, completion: nil)
tableView.endUpdates()

Notice performBatchUpdates is only available in iOS 11⬆️

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTariqView Question on Stackoverflow
Solution 1 - IphoneMick MacCallumView Answer on Stackoverflow
Solution 2 - IphonekrisView Answer on Stackoverflow
Solution 3 - IphoneMike_NotGuiltyView Answer on Stackoverflow
Solution 4 - IphoneHamidreza VakilianView Answer on Stackoverflow
Solution 5 - IphonegcampView Answer on Stackoverflow
Solution 6 - IphoneDavid HView Answer on Stackoverflow
Solution 7 - IphoneDenis KutlubaevView Answer on Stackoverflow
Solution 8 - IphonearchanaView Answer on Stackoverflow
Solution 9 - IphoneMichał ZiobroView Answer on Stackoverflow
Solution 10 - IphoneRossPView Answer on Stackoverflow
Solution 11 - IphoneIdelfonso GutierrezView Answer on Stackoverflow