简体   繁体   中英

Expand/collapse UITableView sections with a backing NSFetchedResultsController

    let frc = NSFetchedResultsController(
        fetchRequest: alertsFetchRequest,
        managedObjectContext: self.moc,
        sectionNameKeyPath: "formattedDateDue",
        cacheName: nil)

How can I expand and collapse the sections on my table view when I've used the NSFetchedResultsController to section my records?

I have seen a lot of tutorials that explain expanding and collapsing cells themselves but not anything on sections produced using the fetched results controller.

First, you need an array to keep track of whether each section is expanded or collapsed:

var sectionExpandedInfo : [Bool] = []

After the fetched results controller has done its initial performFetch , populate this array with true for each section (assuming you want sections expanded by default):

sectionExpandedInfo = []
for _ in frc.sections! {
    sectionExpandedInfo.append(true)
}

Amend the numberOfRowsInSection method to return zero if the section is collapsed:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if sectionExpandedInfo[section] { // expanded
        let sectionInfo = self.frc.sections![section]
        return sectionInfo.numberOfObjects
    } else { // collapsed
        return 0
    }
}

To toggle whether a section is expanded or not, I've used a button as the viewForHeaderInSection , with the section name as the title:

override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    if (self.frc.sections!.count > 0) {
        let sectionInfo = self.frc.sections![section]
        let sectionHeaderButton = UIButton(type: .Custom)
        sectionHeaderButton.backgroundColor = UIColor.redColor()
        sectionHeaderButton.setTitle(sectionInfo.name, forState: .Normal)
        sectionHeaderButton.addTarget(self, action: #selector(MasterViewController.toggleSection(_:)), forControlEvents: .TouchUpInside)
        return sectionHeaderButton
    } else {
        return nil
    }
}

and in the toggleSection method I then use the title to determine which header button has been tapped, and expand/collapse the corresponding section:

func toggleSection(sender: UIButton) {
    for (index, frcSection) in self.frc.sections!.enumerate() {
        if sender.titleForState(.Normal) == frcSection.name {
            sectionExpandedInfo[index] = !sectionExpandedInfo[index]
            self.tableView.reloadSections(NSIndexSet(index: index), withRowAnimation: .Automatic)
        }
    }
}

If your FRC inserts or deletes sections, you need to update the sectionExpandedInfo array to include/remove the extra section:

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    switch type {
        case .Insert:
            self.sectionExpandedInfo.insert(true, atIndex: sectionIndex)
            self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        case .Delete:
            self.sectionExpandedInfo.removeAtIndex(sectionIndex)
            self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        default:
            return
    }
}

Again this assumes you want sections expanded by default.

If you want to change the result set, you need to change the predicate in the request and call performFetch() again. Then, you can update your table. However this may cause performance issues. You can consider other, more elaborate techniques to manage your model-view bind, such as having a different fetch results controller for each expanded section. When the user expands a section, create a new fetch results controller, fetch just the objects for that section and update your table view. When the user collapses the section, discard the fetch results controller. This can, however, considerably complicate your table view data source implementation.

Swift 4:

here is the Swift 4 Version of pbasdf great solution:

defining and filling boolean array:

sectionExpandedInfo = []
   for _ in _fetchedResultsController!.sections! {
      sectionExpandedInfo.append(true)
   }

numberOfRowsInSection method:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if sectionExpandedInfo[section] { // expanded
            let sectionInfo = self.fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
            return sectionInfo.numberOfObjects
        } else { // collapsed
            return 0
        }
    }

defining button in section header (I had to replace toggleSelection argument _: to sender: in order to make it work for me:

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    if (self.fetchedResultsController.sections!.count > 0) {
        let sectionInfo = self.fetchedResultsController.sections![section]
        let sectionHeaderButton = UIButton(type: .custom)
        sectionHeaderButton.backgroundColor = UIColor.red
        sectionHeaderButton.setTitle(sectionInfo.name, for: [])
        sectionHeaderButton.addTarget(self, action: #selector(MasterViewController.toggleSection(sender:)), for: .touchUpInside)
        return sectionHeaderButton
    } else {
        return nil
    }
}

toggleSection function:

@objc func toggleSection(sender: UIButton) {
        for (index, frcSection) in self.fetchedResultsController.sections!.enumerated() {
            if sender.title(for: []) == frcSection.name {
                sectionExpandedInfo[index] = !sectionExpandedInfo[index]
                self.tableView.reloadSections(NSIndexSet(index: index) as IndexSet, with: .automatic)
            }
        }
    }

insert or delete sections:

func controller(controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    switch type {
    case .insert:
        self.sectionExpandedInfo.insert(true, at: sectionIndex)
        self.tableView.insertSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
    case .delete:
        self.sectionExpandedInfo.remove(at: sectionIndex)
        self.tableView.deleteSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
    default:
        return
    }
}

again kudos to pbasdf

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM