Expand/collapse section in UITableView in iOS

前端 未结 17 767
野的像风
野的像风 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:33

    in support to @jean.timex solution, use below code if you want to open one section at any time. create a variable like: var expandedSection = -1;

    func toggleSection(_ header: CollapsibleTableViewHeader, section: Int) {
        let collapsed = !sections[section].collapsed
        // Toggle collapse
        sections[section].collapsed = collapsed
        header.setCollapsed(collapsed)
        tableView.reloadSections(NSIndexSet(index: section) as IndexSet, with: .automatic)
        if (expandedSection >= 0 && expandedSection != section){
            sections[expandedSection].collapsed = true
            tableView.reloadSections(NSIndexSet(index: expandedSection) as IndexSet, with: .automatic)
        }
        expandedSection = section;
    }
    
    0 讨论(0)
  • 2020-11-22 09:36

    This is the best way i found to create expandable table view cells

    .h file

      NSMutableIndexSet *expandedSections;
    

    .m file

    if (!expandedSections)
        {
            expandedSections = [[NSMutableIndexSet alloc] init];
        }
       UITableView *masterTable = [[UITableView alloc] initWithFrame:CGRectMake(0,100,1024,648) style:UITableViewStyleGrouped];
        masterTable.delegate = self;
        masterTable.dataSource = self;
        [self.view addSubview:masterTable];
    

    Table view delegate methods

    - (BOOL)tableView:(UITableView *)tableView canCollapseSection:(NSInteger)section
    {
        // if (section>0) return YES;
    
        return YES;
    }
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        // Return the number of sections.
        return 4;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        if ([self tableView:tableView canCollapseSection:section])
        {
            if ([expandedSections containsIndex:section])
            {
                return 5; // return rows when expanded
            }
    
            return 1; // only top row showing
        }
    
        // Return the number of rows in the section.
        return 1;
    }
    
    - (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] ;
        }
    
        // Configure the cell...
    
        if ([self tableView:tableView canCollapseSection:indexPath.section])
        {
            if (!indexPath.row)
            {
                // first row
                cell.textLabel.text = @"Expandable"; // only top row showing
    
                if ([expandedSections containsIndex:indexPath.section])
                {
    
                    UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableContract"]];
                    cell.accessoryView = imView;
                }
                else
                {
    
                    UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableExpand"]];
                    cell.accessoryView = imView;
                }
            }
            else
            {
                // all other rows
                if (indexPath.section == 0) {
                    cell.textLabel.text = @"section one";
                }else if (indexPath.section == 1) {
                    cell.textLabel.text = @"section 2";
                }else if (indexPath.section == 2) {
                    cell.textLabel.text = @"3";
                }else {
                    cell.textLabel.text = @"some other sections";
                }
    
                cell.accessoryView = nil;
                cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
            }
        }
        else
        {
            cell.accessoryView = nil;
            cell.textLabel.text = @"Normal Cell";
    
        }
    
        return cell;
    }
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if ([self tableView:tableView canCollapseSection:indexPath.section])
        {
            if (!indexPath.row)
            {
                // only first row toggles exapand/collapse
                [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
                NSInteger section = indexPath.section;
                BOOL currentlyExpanded = [expandedSections containsIndex:section];
                NSInteger rows;
    
    
                NSMutableArray *tmpArray = [NSMutableArray array];
    
                if (currentlyExpanded)
                {
                    rows = [self tableView:tableView numberOfRowsInSection:section];
                    [expandedSections removeIndex:section];
    
                }
                else
                {
                    [expandedSections addIndex:section];
                    rows = [self tableView:tableView numberOfRowsInSection:section];
                }
    
    
                for (int i=1; i<rows; i++)
                {
                    NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i 
                                                                   inSection:section];
                    [tmpArray addObject:tmpIndexPath];
                }
    
                UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    
                if (currentlyExpanded)
                {
                    [tableView deleteRowsAtIndexPaths:tmpArray 
                                     withRowAnimation:UITableViewRowAnimationTop];
    
                    UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableExpand"]];
                    cell.accessoryView = imView;
                }
                else
                {
                    [tableView insertRowsAtIndexPaths:tmpArray 
                                     withRowAnimation:UITableViewRowAnimationTop];
    
                    UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"UITableContract"]];
                    cell.accessoryView = imView;
                }
            }
        }
    
        NSLog(@"section :%d,row:%d",indexPath.section,indexPath.row);
    
    }
    
    0 讨论(0)
  • 2020-11-22 09:37
    This action will happen in your didSelectRowAtIndexPath, when you will try to hide or show number of cell in a  section
    
    first of all declare a global variable numberOfSectionInMoreInfo in .h file and in your viewDidLoad set suppose to numberOfSectionInMoreInfo = 4.
    
    Now use following logic: 
    
    
     // More info link
            if(row == 3) {
    
                /*Logic: We are trying to hide/show the number of row into more information section */
    
                NSString *log= [NSString stringWithFormat:@"Number of section in more %i",numberOfSectionInMoreInfo];
    
                [objSpineCustomProtocol showAlertMessage:log];
    
                // Check if the number of rows are open or close in view
                if(numberOfSectionInMoreInfo > 4) {
    
                    // close the more info toggle
                    numberOfSectionInMoreInfo = 4;
    
                }else {
    
                    // Open more info toggle
                    numberOfSectionInMoreInfo = 9;
    
                }
    
                //reload this section
                [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
    

    //vKj

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

    I've used a NSDictionary as datasource, this looks like a lot of code, but it's really simple and works very well! how looks here

    I created a enum for the sections

    typedef NS_ENUM(NSUInteger, TableViewSection) {
    
        TableViewSection0 = 0,
        TableViewSection1,
        TableViewSection2,
        TableViewSectionCount
    };
    

    sections property:

    @property (nonatomic, strong) NSMutableDictionary * sectionsDisctionary;
    

    A method returning my sections:

    -(NSArray <NSNumber *> * )sections{
    
        return @[@(TableViewSection0), @(TableViewSection1), @(TableViewSection2)];
    }
    

    And then setup my data soruce:

    -(void)loadAndSetupData{
    
        self.sectionsDisctionary = [NSMutableDictionary dictionary];
    
        NSArray * sections = [self sections];
    
        for (NSNumber * section in sections) {
    
        NSArray * sectionObjects = [self objectsForSection:section.integerValue];
    
        [self.sectionsDisctionary setObject:[NSMutableDictionary dictionaryWithDictionary:@{@"visible" : @YES, @"objects" : sectionObjects}] forKey:section];
        }
    }
    
    -(NSArray *)objectsForSection:(NSInteger)section{
    
        NSArray * objects;
    
        switch (section) {
    
            case TableViewSection0:
    
                objects = @[] // objects for section 0;
                break;
    
            case TableViewSection1:
    
                objects = @[] // objects for section 1;
                break;
    
            case TableViewSection2:
    
                objects = @[] // objects for section 2;
                break;
    
            default:
                break;
        }
    
        return objects;
    }
    

    The next methods, will help you to know when a section is opened, and how to respond to tableview datasource:

    Respond the section to datasource:

    /**
     *  Asks the delegate for a view object to display in the header of the specified section of the table view.
     *
     *  @param tableView The table-view object asking for the view object.
     *  @param section   An index number identifying a section of tableView .
     *
     *  @return A view object to be displayed in the header of section .
     */
    - (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
    
        NSString * headerName = [self titleForSection:section];
    
        YourCustomSectionHeaderClass * header = (YourCustomSectionHeaderClass *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:YourCustomSectionHeaderClassIdentifier];
    
        [header setTag:section];
        [header addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]];
        header.title = headerName;
        header.collapsed = [self sectionIsOpened:section];
    
    
        return header;
    }
    
    /**
     * Asks the data source to return the number of sections in the table view
     *
     * @param An object representing the table view requesting this information.
     * @return The number of sections in tableView.
     */
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
        // Return the number of sections.
    
        return self.sectionsDisctionary.count;
    }
    
    /**
     * Tells the data source to return the number of rows in a given section of a table view
     *
     * @param tableView: The table-view object requesting this information.
     * @param section: An index number identifying a section in tableView.
     * @return The number of rows in section.
     */
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    
        BOOL sectionOpened = [self sectionIsOpened:section];
        return sectionOpened ? [[self objectsForSection:section] count] : 0;
    }
    

    Tools:

    /**
     Return the section at the given index
    
     @param index the index
    
     @return The section in the given index
     */
    -(NSMutableDictionary *)sectionAtIndex:(NSInteger)index{
    
        NSString * asectionKey = [self.sectionsDisctionary.allKeys objectAtIndex:index];
    
        return [self.sectionsDisctionary objectForKey:asectionKey];
    }
    
    /**
     Check if a section is currently opened
    
     @param section the section to check
    
     @return YES if is opened
     */
    -(BOOL)sectionIsOpened:(NSInteger)section{
    
        NSDictionary * asection = [self sectionAtIndex:section];
        BOOL sectionOpened = [[asection objectForKey:@"visible"] boolValue];
    
        return sectionOpened;
    }
    
    
    /**
     Handle the section tap
    
     @param tap the UITapGestureRecognizer
     */
    - (void)handleTapGesture:(UITapGestureRecognizer*)tap{
    
        NSInteger index = tap.view.tag;
    
        [self toggleSection:index];
    }
    

    Toggle section visibility

    /**
     Switch the state of the section at the given section number
    
     @param section the section number
     */
    -(void)toggleSection:(NSInteger)section{
    
        if (index >= 0){
    
            NSMutableDictionary * asection = [self sectionAtIndex:section];
    
            [asection setObject:@(![self sectionIsOpened:section]) forKey:@"visible"];
    
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
        }
    }
    
    0 讨论(0)
  • 2020-11-22 09:44

    I got a nice solution inspired by Apple's Table View Animations and Gestures. I deleted unnecessary parts from Apple's sample and translated it into swift.

    I know the answer is quite long, but all the code is necessary. Fortunately, you can just copy&past most of the code and just need to do a bit modification on the step 1 and 3

    1.create SectionHeaderView.swift and SectionHeaderView.xib

    import UIKit
    
    protocol SectionHeaderViewDelegate {
        func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionOpened: Int)
        func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionClosed: Int)
    }
    
    class SectionHeaderView: UITableViewHeaderFooterView {
    
        var section: Int?
        @IBOutlet weak var titleLabel: UILabel!
        @IBOutlet weak var disclosureButton: UIButton!
        @IBAction func toggleOpen() {
            self.toggleOpenWithUserAction(true)
        }
        var delegate: SectionHeaderViewDelegate?
    
        func toggleOpenWithUserAction(userAction: Bool) {
            self.disclosureButton.selected = !self.disclosureButton.selected
    
            if userAction {
                if self.disclosureButton.selected {
                    self.delegate?.sectionHeaderView(self, sectionClosed: self.section!)
                } else {
                    self.delegate?.sectionHeaderView(self, sectionOpened: self.section!)
                }
            }
        }
    
        override func awakeFromNib() {
            var tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "toggleOpen")
            self.addGestureRecognizer(tapGesture)
            // change the button image here, you can also set image via IB.
            self.disclosureButton.setImage(UIImage(named: "arrow_up"), forState: UIControlState.Selected)
            self.disclosureButton.setImage(UIImage(named: "arrow_down"), forState: UIControlState.Normal)
        }
    
    }
    

    the SectionHeaderView.xib(the view with gray background) should look something like this in a tableview(you can customize it according to your needs, of course): enter image description here

    note:

    a) the toggleOpen action should be linked to disclosureButton

    b) the disclosureButton and toggleOpen action are not necessary. You can delete these 2 things if you don't need the button.

    2.create SectionInfo.swift

    import UIKit
    
    class SectionInfo: NSObject {
        var open: Bool = true
        var itemsInSection: NSMutableArray = []
        var sectionTitle: String?
    
        init(itemsInSection: NSMutableArray, sectionTitle: String) {
            self.itemsInSection = itemsInSection
            self.sectionTitle = sectionTitle
        }
    }
    

    3.in your tableview

    import UIKit
    
    class TableViewController: UITableViewController, SectionHeaderViewDelegate  {
    
        let SectionHeaderViewIdentifier = "SectionHeaderViewIdentifier"
    
        var sectionInfoArray: NSMutableArray = []
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let sectionHeaderNib: UINib = UINib(nibName: "SectionHeaderView", bundle: nil)
            self.tableView.registerNib(sectionHeaderNib, forHeaderFooterViewReuseIdentifier: SectionHeaderViewIdentifier)
    
            // you can change section height based on your needs
            self.tableView.sectionHeaderHeight = 30
    
            // You should set up your SectionInfo here
            var firstSection: SectionInfo = SectionInfo(itemsInSection: ["1"], sectionTitle: "firstSection")
            var secondSection: SectionInfo = SectionInfo(itemsInSection: ["2"], sectionTitle: "secondSection"))
            sectionInfoArray.addObjectsFromArray([firstSection, secondSection])
        }
    
        // MARK: - Table view data source
    
        override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
            return sectionInfoArray.count
        }
    
        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            if self.sectionInfoArray.count > 0 {
                var sectionInfo: SectionInfo = sectionInfoArray[section] as! SectionInfo
                if sectionInfo.open {
                    return sectionInfo.open ? sectionInfo.itemsInSection.count : 0
                }
            }
            return 0
        }
    
        override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
            let sectionHeaderView: SectionHeaderView! = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier(SectionHeaderViewIdentifier) as! SectionHeaderView
            var sectionInfo: SectionInfo = sectionInfoArray[section] as! SectionInfo
    
            sectionHeaderView.titleLabel.text = sectionInfo.sectionTitle
            sectionHeaderView.section = section
            sectionHeaderView.delegate = self
            let backGroundView = UIView()
            // you can customize the background color of the header here
            backGroundView.backgroundColor = UIColor(red:0.89, green:0.89, blue:0.89, alpha:1)
            sectionHeaderView.backgroundView = backGroundView
            return sectionHeaderView
        }
    
        func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionOpened: Int) {
            var sectionInfo: SectionInfo = sectionInfoArray[sectionOpened] as! SectionInfo
            var countOfRowsToInsert = sectionInfo.itemsInSection.count
            sectionInfo.open = true
    
            var indexPathToInsert: NSMutableArray = NSMutableArray()
            for i in 0..<countOfRowsToInsert {
                indexPathToInsert.addObject(NSIndexPath(forRow: i, inSection: sectionOpened))
            }
            self.tableView.insertRowsAtIndexPaths(indexPathToInsert as [AnyObject], withRowAnimation: .Top)
        }
    
        func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionClosed: Int) {
            var sectionInfo: SectionInfo = sectionInfoArray[sectionClosed] as! SectionInfo
            var countOfRowsToDelete = sectionInfo.itemsInSection.count
            sectionInfo.open = false
            if countOfRowsToDelete > 0 {
                var indexPathToDelete: NSMutableArray = NSMutableArray()
                for i in 0..<countOfRowsToDelete {
                    indexPathToDelete.addObject(NSIndexPath(forRow: i, inSection: sectionClosed))
                }
                self.tableView.deleteRowsAtIndexPaths(indexPathToDelete as [AnyObject], withRowAnimation: .Top)
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 09:44

    I am adding this solution for completeness and showing how to work with section headers.

    import UIKit
    
    class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
        @IBOutlet var tableView: UITableView!
        var headerButtons: [UIButton]!
        var sections = [true, true, true]
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.dataSource = self
            tableView.delegate = self
    
            let section0Button = UIButton(type: .detailDisclosure)
            section0Button.setTitle("Section 0", for: .normal)
            section0Button.addTarget(self, action: #selector(section0Tapped), for: .touchUpInside)
    
            let section1Button = UIButton(type: .detailDisclosure)
            section1Button.setTitle("Section 1", for: .normal)
            section1Button.addTarget(self, action: #selector(section1Tapped), for: .touchUpInside)
    
            let section2Button = UIButton(type: .detailDisclosure)
            section2Button.setTitle("Section 2", for: .normal)
            section2Button.addTarget(self, action: #selector(section2Tapped), for: .touchUpInside)
    
            headerButtons = [UIButton]()
            headerButtons.append(section0Button)
            headerButtons.append(section1Button)
            headerButtons.append(section2Button)
        }
    
        func numberOfSections(in tableView: UITableView) -> Int {
            return sections.count
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return sections[section] ? 3 : 0
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cellReuseId = "cellReuseId"
            let cell = UITableViewCell(style: .default, reuseIdentifier: cellReuseId)
            cell.textLabel?.text = "\(indexPath.section): \(indexPath.row)"
            return cell
        }
    
        func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
            return headerButtons[section]
        }
    
        func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
            return 44
        }
    
        @objc func section0Tapped() {
            sections[0] = !sections[0]
            tableView.reloadSections([0], with: .fade)
        }
    
        @objc func section1Tapped() {
            sections[1] = !sections[1]
            tableView.reloadSections([1], with: .fade)
        }
    
        @objc func section2Tapped() {
            sections[2] = !sections[2]
            tableView.reloadSections([2], with: .fade)
        }
    
    }
    

    Link to gist: https://gist.github.com/pawelkijowskizimperium/fe1e8511a7932a0d40486a2669316d2c

    0 讨论(0)
提交回复
热议问题