Mike_NotGuilty
Mike_NotGuilty

Reputation: 2405

iOS - Expand/Collapse UITableView section with animations

I want to expand/collapse my UITableView sections with animations. I used this answer and it works now if I call self.tableView.reloadData(). But I want that when I tap on my custom UITableView- header , the cells of the section should slide down/up with a nice animation. I tried to use self.tableView.beginUpdates() and self.tableView.endUpdates(), but I get this error:

Invalid update: invalid number of rows in section 0.  The number of rows contained in an 
existing section after the update (8) must be equal to the number of rows contained in that 
section before the update (0), plus or minus the number of rows inserted or deleted from 
that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out 
of that section (0 moved in, 0 moved out).

Here's some code. The method that is called when I tap on the section:

func expand(sender:UITapGestureRecognizer){

    let tag = (sender.view?.tag)!

    self.tableView.beginUpdates()
    if ausgeklappt[tag] { ausgeklappt[tag] = false }
    else { ausgeklappt[tag] = true }

    self.tableView.endUpdates()
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // Return the number of rows in the section.
    let keyDerSection = sortSpieleDict.keys.array[section]
    let arrayDerSection = sortSpieleDict[keyDerSection]!
    if ausgeklappt[section] == false { return 0 } 
    else { return arrayDerSection.count }
}

Thanks.

Upvotes: 3

Views: 20092

Answers (4)

Saurabh Bisht
Saurabh Bisht

Reputation: 408

Basically tableview beginupdates & endplates is used when there is a change in the section-row model and we update delete or insert few rows.

However your problem must be from the cellforHeight or cellforRowAtIndex where you need to put the section in switch case.

Thanks

Upvotes: 1

Gami Nilesh
Gami Nilesh

Reputation: 581

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self initialization];
}

#pragma  mark - Initialization

-(void)initialization
{
    arrayForBool=[[NSMutableArray alloc]init];
    dic_data =[[NSMutableDictionary alloc]init];
    [dic_data setValue:@[@"1",@"2"] forKey:@"Section"];
    [dic_data setValue:@[@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8"] forKey:@"Section1"];
    [dic_data setValue:@[@"1",@"2",@"3",@"4"] forKey:@"Section2"];

    for (int i=0; i<dic_data.allKeys.count; i++) {
        [arrayForBool addObject:[NSNumber numberWithBool:NO]];
    }
}


#pragma mark -
#pragma mark TableView DataSource and Delegate Methods

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

    if ([[arrayForBool objectAtIndex:section] boolValue]) {
        NSString *str =[dic_data.allKeys objectAtIndex:section];
        return [[dic_data valueForKey:str]count];
    }
    else
    return 0;

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellid=@"hello";
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellid];
    if (cell==nil) {
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellid];
    }
    NSString *str =[dic_data.allKeys objectAtIndex:indexPath.section];

    cell.textLabel.text = [[dic_data valueForKey:str]objectAtIndex:indexPath.row];
    return cell;
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return dic_data.allKeys.count;
}

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

    /*************** Close the section, once the data is selected ***********************************/
    [arrayForBool replaceObjectAtIndex:indexPath.section withObject:[NSNumber numberWithBool:NO]];

     [_expandableTableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationAutomatic];

}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ([[arrayForBool objectAtIndex:indexPath.section] boolValue]) {
        return 40;
    }
    return 0;

}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 40;
}

#pragma mark - Creating View for TableView Section

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

    UIView *sectionView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, 280,40)];
    sectionView.tag=section;
    UILabel *viewLabel=[[UILabel alloc]initWithFrame:CGRectMake(10, 0, _expandableTableView.frame.size.width-10, 40)];
    viewLabel.backgroundColor=[UIColor clearColor];
    viewLabel.textColor=[UIColor blackColor];
    viewLabel.font=[UIFont systemFontOfSize:15];
    NSString *str =[dic_data.allKeys objectAtIndex:section];

    viewLabel.text=[NSString stringWithFormat:@"List of %@",str];
    [sectionView addSubview:viewLabel];

    /********** Add UITapGestureRecognizer to SectionView   **************/
    UITapGestureRecognizer  *headerTapped   = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
    [sectionView addGestureRecognizer:headerTapped];

    return  sectionView;


}


#pragma mark - Table header gesture tapped

- (void)sectionHeaderTapped:(UITapGestureRecognizer *)gestureRecognizer{

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:gestureRecognizer.view.tag];
    if (indexPath.row == 0) {
        BOOL collapsed  = [[arrayForBool objectAtIndex:indexPath.section] boolValue];
        for (int i=0; i<dic_data.allKeys.count; i++) {
            if (indexPath.section==i) {
                [arrayForBool replaceObjectAtIndex:i withObject:[NSNumber numberWithBool:!collapsed]];
            }
        }
        [_expandableTableView reloadSections:[NSIndexSet indexSetWithIndex:gestureRecognizer.view.tag] withRowAnimation:UITableViewRowAnimationTop];

    }

}

I have written this code in Objective-C as i don't have much knowledge of swift yet. This is just for the logic. Please convert the code according to your need.

Upvotes: 0

Mike_NotGuilty
Mike_NotGuilty

Reputation: 2405

Thanks to iOS_DEV I found a solution: Just one line of code did the trick. I just replaced the beginUpdates() and endUpdates() with the reloadSections() method. Now it works fine!

    func expand(sender:UITapGestureRecognizer){

        let tag = (sender.view?.tag)! // The tag value is the section of my custom UITabelView header view.

        if ausgeklappt[tag] { ausgeklappt[tag] = false }
        else { ausgeklappt[tag] = true }

        // The next line did the trick! 
        self.tableView.reloadSections(NSIndexSet(index: tag), withRowAnimation: UITableViewRowAnimation.Automatic)
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Return the number of rows in the section.
        let keyDerSection = sortSpieleDict.keys.array[section]
        let arrayDerSection = sortSpieleDict[keyDerSection]!

        if ausgeklappt[section] == false 
        { 
             return 0 
        } 
        else 
        { 
             return arrayDerSection.count 
        }
    }

Upvotes: 10

iOS_DEV
iOS_DEV

Reputation: 921

i used a NSMutableSet to keep track of clicked headers. I placed a UIButton on every header which responded to the following event:

#pragma mark - Header Clicked
-(void) headerClicked:(UIButton *) sender{

    if ([set_OpenIndex containsObject:[NSNumber numberWithUnsignedInteger:sender.tag]]) {
        [set_OpenIndex removeObject:[NSNumber numberWithUnsignedInteger:sender.tag]];
        [tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationAutomatic];
    }
    else{

        if (set_OpenIndex.count > 0) {
            //--- a header is opened already, close the previous one before opening the other

            [UIView animateWithDuration:0.5 animations:^{
                [set_OpenIndex enumerateObjectsUsingBlock:^(id obj, BOOL *stop){
                    [set_OpenIndex removeObject:obj];
                    [tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:[obj integerValue]] withRowAnimation:UITableViewRowAnimationAutomatic];

                }];
            } completion:^(BOOL finished){

                [set_OpenIndex addObject:[NSNumber numberWithUnsignedInteger:sender.tag]];
                [tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationAutomatic];

            }];
        }
        else{
            [set_OpenIndex addObject:[NSNumber numberWithUnsignedInteger:sender.tag]];
            [tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationAutomatic];
        }
    }
}

And just set up the number of rows as follows:

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

    if ([set_OpenIndex containsObject:[NSNumber numberWithInteger:section]]) {
            return 5; // or what ever is the number of rows
        }

        return 0;
}

I have written this code in Objective-C as i don't have much knowledge of swift yet. This is just for the logic. Please convert the code according to your need.

Note: don't forget to set the tag of the UIButton in header according to the section number.

Upvotes: 2

Related Questions