Expand/collapse section in UITableView in iOS

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

    You have to make your own custom header row and put that as the first row of each section. Subclassing the UITableView or the headers that are already there will be a pain. Based on the way they work now, I am not sure you can easily get actions out of them. You could set up a cell to LOOK like a header, and setup the tableView:didSelectRowAtIndexPath to manually expand or collapse the section it is in.

    I'd store an array of booleans corresponding the the "expended" value of each of your sections. Then you could have the tableView:didSelectRowAtIndexPath on each of your custom header rows toggle this value and then reload that specific section.

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        if (indexPath.row == 0) {
            ///it's the first row of any section so it would be your custom section header
    
            ///put in your code to toggle your boolean value here
            mybooleans[indexPath.section] = !mybooleans[indexPath.section];
    
            ///reload this section
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
        }
    }
    

    Then set numberOfRowsInSection to check the mybooleans value and return 1 if the section isn't expanded, or 1+ the number of items in the section if it is expanded.

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    
        if (mybooleans[section]) {
            ///we want the number of people plus the header cell
            return [self numberOfPeopleInGroup:section] + 1;
        } else {
            ///we just want the header cell
            return 1;
        }
    }
    

    Also, you will need to update cellForRowAtIndexPath to return a custom header cell for the first row in any section.

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

    I have a better solution that you should add a UIButton into section header and set this button's size equal to section size, but make it hidden by clear background color, after that you are easily to check which section is clicked to expand or collapse

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

    So, based on the 'button in header' solution, here is a clean and minimalist implementation:

    • you keep track of collapsed (or expanded) sections in a property
    • you tag the button with the section index
    • you set a selected state on that button to change the arrow direction (like △ and ▽)

    Here is the code:

    @interface MyTableViewController ()
    @property (nonatomic, strong) NSMutableIndexSet *collapsedSections;
    @end
    
    ...
    
    @implementation MyTableViewController
    
    - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    {
        self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
        if (!self)
            return;
        self.collapsedSections = [NSMutableIndexSet indexSet];
        return self;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        // if section is collapsed
        if ([self.collapsedSections containsIndex:section])
            return 0;
    
        // if section is expanded
    #warning incomplete implementation
        return [super tableView:tableView numberOfRowsInSection:section];
    }
    
    - (IBAction)toggleSectionHeader:(UIView *)sender
    {
        UITableView *tableView = self.tableView;
        NSInteger section = sender.tag;
    
        MyTableViewHeaderFooterView *headerView = (MyTableViewHeaderFooterView *)[self tableView:tableView viewForHeaderInSection:section];
    
        if ([self.collapsedSections containsIndex:section])
        {
            // section is collapsed
            headerView.button.selected = YES;
            [self.collapsedSections removeIndex:section];
        }
        else
        {
            // section is expanded
            headerView.button.selected = NO;
            [self.collapsedSections addIndex:section];
        }
    
        [tableView beginUpdates];
        [tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView endUpdates];
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-22 09:31

    To implement the collapsible table section in iOS, the magic is how to control the number of rows for each section, or we can manage the height of rows for each section.

    Also, we need to customize the section header so that we can listen to the tap event from the header area (whether it's a button or the whole header).

    How to deal with the header? It's very simple, we extend the UITableViewCell class and make a custom header cell like so:

    import UIKit
    
    class CollapsibleTableViewHeader: UITableViewCell {
    
        @IBOutlet var titleLabel: UILabel!
        @IBOutlet var toggleButton: UIButton!
    
    }
    

    then use the viewForHeaderInSection to hook up the header cell:

    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
      let header = tableView.dequeueReusableCellWithIdentifier("header") as! CollapsibleTableViewHeader
    
      header.titleLabel.text = sections[section].name
      header.toggleButton.tag = section
      header.toggleButton.addTarget(self, action: #selector(CollapsibleTableViewController.toggleCollapse), forControlEvents: .TouchUpInside)
    
      header.toggleButton.rotate(sections[section].collapsed! ? 0.0 : CGFloat(M_PI_2))
    
      return header.contentView
    }
    

    remember we have to return the contentView because this function expects a UIView to be returned.

    Now let's deal with the collapsible part, here is the toggle function that toggle the collapsible prop of each section:

    func toggleCollapse(sender: UIButton) {
      let section = sender.tag
      let collapsed = sections[section].collapsed
    
      // Toggle collapse
      sections[section].collapsed = !collapsed
    
      // Reload section
      tableView.reloadSections(NSIndexSet(index: section), withRowAnimation: .Automatic)
    }
    

    depends on how you manage the section data, in this case, I have the section data something like this:

    struct Section {
      var name: String!
      var items: [String]!
      var collapsed: Bool!
    
      init(name: String, items: [String]) {
        self.name = name
        self.items = items
        self.collapsed = false
      }
    }
    
    var sections = [Section]()
    
    sections = [
      Section(name: "Mac", items: ["MacBook", "MacBook Air", "MacBook Pro", "iMac", "Mac Pro", "Mac mini", "Accessories", "OS X El Capitan"]),
      Section(name: "iPad", items: ["iPad Pro", "iPad Air 2", "iPad mini 4", "Accessories"]),
      Section(name: "iPhone", items: ["iPhone 6s", "iPhone 6", "iPhone SE", "Accessories"])
    ]
    

    at last, what we need to do is based on the collapsible prop of each section, control the number of rows of that section:

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return (sections[section].collapsed!) ? 0 : sections[section].items.count
    }
    

    I have a fully working demo on my Github: https://github.com/jeantimex/ios-swift-collapsible-table-section

    If you want to implement the collapsible sections in a grouped-style table, I have another demo with source code here: https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section

    Hope that helps.

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

    Some sample code for animating an expand/collapse action using a table view section header is provided by Apple at 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:32

    I have done the same thing using multiple sections .

    class SCTierBenefitsViewController: UIViewController {
        @IBOutlet private weak var tblTierBenefits: UITableView!
        private var selectedIndexPath: IndexPath?
        private var isSelected:Bool = false
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tblTierBenefits.register(UINib(nibName:"TierBenefitsTableViewCell", bundle: nil), forCellReuseIdentifier:"TierBenefitsTableViewCell")
            tblTierBenefits.register(UINib(nibName:"TierBenefitsDetailsCell", bundle: nil), forCellReuseIdentifier:"TierBenefitsDetailsCell")
    
            tblTierBenefits.rowHeight = UITableViewAutomaticDimension;
            tblTierBenefits.estimatedRowHeight = 44.0;
            tblTierBenefits.tableFooterView = UIView()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
    }
    
    extension SCTierBenefitsViewController : UITableViewDataSource{
    
        func numberOfSections(in tableView: UITableView) -> Int {
            return 7
        }
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return (isSelected && section == selectedIndexPath?.section) ? 2 : 1 
        }
    
        func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
            return  0.01
        }
    
        func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
            return nil
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            switch indexPath.row {
            case 0:
                let cell:TierBenefitsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "TierBenefitsTableViewCell")! as! TierBenefitsTableViewCell
                cell.selectionStyle = .none
                cell.contentView.setNeedsLayout()
                cell.contentView.layoutIfNeeded()
                return cell
    
            case 1:
                let cell:TierBenefitsDetailsCell = tableView.dequeueReusableCell(withIdentifier: "TierBenefitsDetailsCell")! as! TierBenefitsDetailsCell
                cell.selectionStyle = .none
                return cell
    
            default:
                break
            }
    
            return UITableViewCell()
        }
    }
    
    extension SCTierBenefitsViewController : UITableViewDelegate{
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            if indexPath.row == 0 {
    
                if let _selectedIndexPath = selectedIndexPath ,selectedIndexPath?.section == indexPath.section {
                    tblTierBenefits.beginUpdates()
                    expandCollapse(indexPath: _selectedIndexPath, isExpand: false)
                    selectedIndexPath = nil
                }
                else{
                    tblTierBenefits.beginUpdates()
                    if selectedIndexPath != nil {
                        tblTierBenefits.reloadSections([(selectedIndexPath?.section)!], with: .none)
                    }
                    expandCollapse(indexPath: indexPath, isExpand: true)
                }
            }
        }
    
        private func  expandCollapse(indexPath: IndexPath?,isExpand: Bool){
            isSelected = isExpand
            selectedIndexPath = indexPath
            tblTierBenefits.reloadSections([(indexPath?.section)!], with: .none)
            tblTierBenefits.endUpdates()
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题