Expand/collapse section in UITableView in iOS

前端 未结 17 766
野的像风
野的像风 2020-11-22 08:49

Could somebody tell me the way to perform UITableView expandable/collapsible animations in sections of UITableView as below?

<

相关标签:
17条回答
  • 2020-11-22 09:45

    I found another relatively simple way to solve that problem. By using this method we will not required to alter our cell which is almost always related to data array index, potentially causing mess in our view controller.

    First, we add this following properties to our controller class:

    @property (strong, nonatomic) NSMutableArray* collapsedSections;
    @property (strong, nonatomic) NSMutableArray* sectionViews;
    

    collapsedSections will save collapsed section numbers. sectionViews will store our custom section view.

    Synthesize it:

    @synthesize collapsedSections;
    @synthesize sectionViews;
    

    Initialize it:

    - (void) viewDidLoad
    {
        [super viewDidLoad];
    
        self.collapsedSections = [NSMutableArray array];
        self.sectionViews      = [NSMutableArray array];
    }
    

    After that, we must connect our UITableView so it can be accessed from within our view controller class:

    @property (strong, nonatomic) IBOutlet UITableView *tblMain;
    

    Connect it from XIB to view controller using ctrl + drag like usually.

    Then we create view as custom section header for our table view by implementing this UITableView delegate:

    - (UIView*) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
    {
        // Create View
        CGRect frame = CGRectZero;
    
        frame.origin = CGPointZero;
    
        frame.size.height = 30.f;
        frame.size.width  = tableView.bounds.size.width;
    
        UIView* view = [[UIView alloc] initWithFrame:frame];
    
        [view setBackgroundColor:[UIColor blueColor]];
    
        // Add label for title
        NSArray* titles = @[@"Title 1", @"Title 2", @"Title 3"];
    
        NSString* selectedTitle = [titles objectAtIndex:section];
    
        CGRect labelFrame = frame;
    
        labelFrame.size.height = 30.f;
        labelFrame.size.width -= 20.f;
        labelFrame.origin.x += 10.f;
    
        UILabel* titleLabel = [[UILabel alloc] initWithFrame:labelFrame];
    
        [titleLabel setText:selectedTitle];
        [titleLabel setTextColor:[UIColor whiteColor]];
    
        [view addSubview:titleLabel];
    
        // Add touch gesture
        [self attachTapGestureToView:view];
    
        // Save created view to our class property array
        [self saveSectionView:view inSection:section];
    
        return view;
    }
    

    Next, we implement method to save our previously created custom section header in class property:

    - (void) saveSectionView:(UIView*) view inSection:(NSInteger) section
    {
        NSInteger sectionCount = [self numberOfSectionsInTableView:[self tblMain]];
    
        if(section < sectionCount)
        {
            if([[self sectionViews] indexOfObject:view] == NSNotFound)
            {
                [[self sectionViews] addObject:view];
            }
        }
    }
    

    Add UIGestureRecognizerDelegate to our view controller .h file:

    @interface MyViewController : UIViewController<UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate>
    

    Then we create method attachTapGestureToView:

    - (void) attachTapGestureToView:(UIView*) view
    {
        UITapGestureRecognizer* tapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
    
        [tapAction setDelegate:self];
    
        [view addGestureRecognizer:tapAction];
    }
    

    Above method will add tap gesture recognizer to all of section view we created before. Next we should implement onTap: selector

    - (void) onTap:(UITapGestureRecognizer*) gestureRecognizer
    {
        // Take view who attach current recognizer
        UIView* sectionView = [gestureRecognizer view]; 
    
        // [self sectionViews] is Array containing our custom section views
        NSInteger section = [self sectionNumberOfView:sectionView];
    
        // [self tblMain] is our connected IBOutlet table view
        NSInteger sectionCount = [self numberOfSectionsInTableView:[self tblMain]];
    
        // If section more than section count minus one set at last
        section = section > (sectionCount - 1) ? 2 : section;
    
        [self toggleCollapseSection:section];
    }
    

    Above method will invoked when user tap any of our table view section. This method search correct section number based on our sectionViews array we created before.

    Also, we implement method to get wihch section of header view belongs to.

    - (NSInteger) sectionNumberOfView:(UIView*) view
    {
        UILabel* label = [[view subviews] objectAtIndex:0];
    
        NSInteger sectionNum = 0;
    
        for(UIView* sectionView in [self sectionViews])
        {
            UILabel* sectionLabel = [[sectionView subviews] objectAtIndex:0];
    
            //NSLog(@"Section: %d -> %@ vs %@", sectionNum, [label text], [sectionLabel text]);
    
            if([[label text] isEqualToString:[sectionLabel text]])
            {
                return sectionNum;
            }
    
            sectionNum++;
        }
    
        return NSNotFound;
    }
    

    Next, we must implement method toggleCollapseSection:

    - (void) toggleCollapseSection:(NSInteger) section
    {
        if([self isCollapsedSection:section])
        {
            [self removeCollapsedSection:section];
        }
        else
        {
            [self addCollapsedSection:section];
        }
    
        [[self tblMain] reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
    }
    

    This method will insert/remove section number to our collapsedSections array we created before. When a section number inserted to that array, it means that the section should be collapsed and expanded if otherwise.

    Next we implement removeCollapsedSection:, addCollapsedSection:section and isCollapsedSection:section

    - (BOOL)isCollapsedSection:(NSInteger) section
    {
        for(NSNumber* existing in [self collapsedSections])
        {
            NSInteger current = [existing integerValue];
    
            if(current == section)
            {
                return YES;
            }
        }
    
        return NO;
    }
    
    - (void)removeCollapsedSection:(NSInteger) section
    {
        [[self collapsedSections] removeObjectIdenticalTo:[NSNumber numberWithInteger:section]];
    }
    
    - (void)addCollapsedSection:(NSInteger) section
    {
        [[self collapsedSections] addObject:[NSNumber numberWithInteger:section]];
    }
    

    This three method is just helpers to make us easier in accessing collapsedSections array.

    Finally, implement this table view delegate so our custom section views looks nice.

    - (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    {
        return 30.f; // Same as each custom section view height
    }
    

    Hope it helps.

    0 讨论(0)
  • 2020-11-22 09:46
    // -------------------------------------------------------------------------------
    //  tableView:viewForHeaderInSection:
    // -------------------------------------------------------------------------------
    - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    
        UIView *mView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 20, 20)];
        [mView setBackgroundColor:[UIColor greenColor]];
    
        UIImageView *logoView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 5, 20, 20)];
        [logoView setImage:[UIImage imageNamed:@"carat.png"]];
        [mView addSubview:logoView];
    
        UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
        [bt setFrame:CGRectMake(0, 0, 150, 30)];
        [bt setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        [bt setTag:section];
        [bt.titleLabel setFont:[UIFont systemFontOfSize:20]];
        [bt.titleLabel setTextAlignment:NSTextAlignmentCenter];
        [bt.titleLabel setTextColor:[UIColor blackColor]];
        [bt setTitle: @"More Info" forState: UIControlStateNormal];
        [bt addTarget:self action:@selector(addCell:) forControlEvents:UIControlEventTouchUpInside];
        [mView addSubview:bt];
        return mView;
    
    }
    
    #pragma mark - Suppose you want to hide/show section 2... then
    #pragma mark  add or remove the section on toggle the section header for more info
    
    - (void)addCell:(UIButton *)bt{
    
        // If section of more information
        if(bt.tag == 2) {
    
            // Initially more info is close, if more info is open
            if(ifOpen) {
                DLog(@"close More info");
    
                // Set height of section
                heightOfSection = 0.0f;
    
                // Reset the parameter that more info is closed now
                ifOpen = NO;
            }else {
                // Set height of section
                heightOfSection = 45.0f;
                // Reset the parameter that more info is closed now
                DLog(@"open more info again");
                ifOpen = YES;
            }
            //[self.tableView reloadData];
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:2] withRowAnimation:UITableViewRowAnimationFade];
        }
    
    }// end addCell
    #pragma mark -
    #pragma mark  What will be the height of the section, Make it dynamic
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    
        if (indexPath.section == 2) {
            return heightOfSection;
        }else {
            return 45.0f;
        }
    

    // vKj

    0 讨论(0)
  • 2020-11-22 09:50

    I ended up just creating a headerView that contained a button ( i saw Son Nguyen's solution above after the fact, but heres my code.. it looks like a lot but it's pretty simple):

    declare a couple bools for you sections

    bool customerIsCollapsed = NO;
    bool siteIsCollapsed = NO;
    

    ...code

    now in your tableview delegate methods...

    - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
    {
        UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _tblSearchResults.frame.size.width, 35)];
    
        UILabel *lblSection = [UILabel new];
        [lblSection setFrame:CGRectMake(0, 0, 300, 30)];
        [lblSection setFont:[UIFont fontWithName:@"Helvetica-Bold" size:17]];
        [lblSection setBackgroundColor:[UIColor clearColor]];
        lblSection.alpha = 0.5;
        if(section == 0)
        {
            if(!customerIsCollapsed)
                [lblSection setText:@"Customers    --touch to show--"];
            else
                [lblSection setText:@"Customers    --touch to hide--"];
        }
        else
        {
            if(!siteIsCollapsed)
                [lblSection setText:@"Sites    --touch to show--"];
            else
                [lblSection setText:@"Sites    --touch to hide--"];    }
    
        UIButton *btnCollapse = [UIButton buttonWithType:UIButtonTypeCustom];
        [btnCollapse setFrame:CGRectMake(0, 0, _tblSearchResults.frame.size.width, 35)];
        [btnCollapse setBackgroundColor:[UIColor clearColor]];
        [btnCollapse addTarget:self action:@selector(touchedSection:) forControlEvents:UIControlEventTouchUpInside];
        btnCollapse.tag = section;
    
    
        [headerView addSubview:lblSection];
        [headerView addSubview:btnCollapse];
    
        return headerView;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        // Return the number of rows in the section.
        if(section == 0)
        {
            if(customerIsCollapsed)
                return 0;
            else
                return _customerArray.count;
        }
        else if (section == 1)
        {
            if(siteIsCollapsed)
                return 0;
            else
            return _siteArray.count;
    
        }
        return 0;
    }
    

    and finally the function that gets called when you touch one of the section header buttons:

    - (IBAction)touchedSection:(id)sender
    {
        UIButton *btnSection = (UIButton *)sender;
    
        if(btnSection.tag == 0)
        {
            NSLog(@"Touched Customers header");
            if(!customerIsCollapsed)
                customerIsCollapsed = YES;
            else
                customerIsCollapsed = NO;
    
        }
        else if(btnSection.tag == 1)
        {
            NSLog(@"Touched Site header");
            if(!siteIsCollapsed)
                siteIsCollapsed = YES;
            else
                siteIsCollapsed = NO;
    
        }
        [_tblSearchResults reloadData];
    }
    
    0 讨论(0)
  • 2020-11-22 09:51

    Some sample code for animating an expand/collapse action using a table view section header is provided by Apple here: Table View Animations and Gestures

    The key to this approach is to implement - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section and return a custom UIView which includes a button (typically the same size as the header view itself). By subclassing UIView and using that for the header view (as this sample does), you can easily store additional data such as the section number.

    0 讨论(0)
  • 2020-11-22 09:52

    Expanding on this answer written in Objective C, I wrote the following for those writing in Swift

    The idea is to use sections within the table and set the number of rows in the section to 1 (collapsed) and 3(expanded) when the first row in that section is tapped

    The table decides how many rows to draw based on an array of Boolean values

    You'll need to create two rows in storyboard and give them the reuse identifiers 'CollapsingRow' and 'GroupHeading'

    import UIKit
    
    class CollapsingTVC:UITableViewController{
    
        var sectionVisibilityArray:[Bool]!// Array index corresponds to section in table
    
        override func viewDidLoad(){
            super.viewDidLoad()
            sectionVisibilityArray = [false,false,false]
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
        }
    
        override func numberOfSections(in tableView: UITableView) -> Int{
            return sectionVisibilityArray.count
        }
        override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat{
            return 0
        }
    
        // numberOfRowsInSection - Get count of entries
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            var rowsToShow:Int = 0
            if(sectionVisibilityArray[section]){
                rowsToShow = 3 // Or however many rows should be displayed in that section
            }else{
                rowsToShow = 1
            }
            return rowsToShow
        }// numberOfRowsInSection
    
    
        override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
            if(indexPath.row == 0){
                if(sectionVisibilityArray[indexPath.section]){
                    sectionVisibilityArray[indexPath.section] = false
                }else{
                    sectionVisibilityArray[indexPath.section] = true
                }
                self.tableView.reloadSections([indexPath.section], with: .automatic)
            }
        }
    
        // cellForRowAtIndexPath - Get table cell corresponding to this IndexPath
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
            var cell:UITableViewCell
    
            if(indexPath.row == 0){
                 cell = tableView.dequeueReusableCell(withIdentifier: "GroupHeading", for: indexPath as IndexPath)
            }else{
                cell = tableView.dequeueReusableCell(withIdentifier: "CollapsingRow", for: indexPath as IndexPath)
            }
    
            return cell
    
        }// cellForRowAtIndexPath
    
    }
    
    0 讨论(0)
提交回复
热议问题