展开/折叠带有支持 NSFetchedResultsController 的 UITableView 部分

Posted

技术标签:

【中文标题】展开/折叠带有支持 NSFetchedResultsController 的 UITableView 部分【英文标题】:Expand/collapse UITableView sections with a backing NSFetchedResultsController 【发布时间】:2016-04-08 19:33:04 【问题描述】:
    let frc = NSFetchedResultsController(
        fetchRequest: alertsFetchRequest,
        managedObjectContext: self.moc,
        sectionNameKeyPath: "formattedDateDue",
        cacheName: nil)

当我使用 NSFetchedResultsController 对记录进行分区时,如何展开和折叠表格视图中的部分?

我看过很多教程,解释了展开和折叠单元格本身,但没有任何关于使用 fetched results controller 生成的部分的内容。

【问题讨论】:

@uday.m 你的编辑不是很好。请不要在问题标题中添加“Swift:”前缀。 【参考方案1】:

首先,您需要一个数组来跟踪每个部分是展开还是折叠:

var sectionExpandedInfo : [Bool] = []

在获取结果控制器完成其初始performFetch 后,为每个部分填充此数组true(假设您希望默认展开部分):

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

修改numberOfRowsInSection 方法以在该部分折叠时返回零:

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
    

为了切换一个部分是否展开,我使用了一个按钮作为viewForHeaderInSection,部分名称作为标题:

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
    

然后在toggleSection 方法中,我使用标题来确定点击了哪个标题按钮,并展开/折叠相应的部分:

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)
        
    

如果您的 FRC 插入或删除部分,您需要更新 sectionExpandedInfo 数组以包含/删除额外部分:

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
    

再次假设您希望默认展开部分。

【讨论】:

完美运行!谢谢,@pbasdf 很好的解决方案。谢谢。也许是一种改进。代替使用按钮标题来确定节,将 sectionHeaderButton.tag = section 添加到 viewForHeaderInSection,然后在 toggleSection 中,您可以参考 sender.tag 来获取节号。 @guido 好主意。避免了笨拙的部分枚举。 删除所有条目后它可能会在didChangeSection 中崩溃,因为索引随机进入导致self.sectionExpandedInfo 上的越界错误。我还没有解决方案。 我有一个使用 NSIndexSets 而不是数组的工作版本。在didChangeSection 中使用NSMutableIndexSet:shiftIndexesStartingAtIndex 来说明由于插入或删除部分而导致的索引更新(请参阅我的其他评论)。【参考方案2】:

如果要更改结果集,则需要更改请求中的谓词并再次调用performFetch()。然后,您可以更新您的表格。但是,这可能会导致性能问题。您可以考虑其他更复杂的技术来管理您的模型视图绑定,例如为每个展开的部分使用不同的获取结果控制器。当用户展开一个部分时,创建一个新的获取结果控制器,只获取该部分的对象并更新您的表格视图。当用户折叠该部分时,丢弃获取结果控制器。但是,这可能会使您的表视图数据源实现变得相当复杂。

【讨论】:

【参考方案3】:

斯威夫特 4:

这里是pbasdf 的 Swift 4 版本,很棒的解决方案:

定义和填充布尔数组:

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

numberOfRowsInSection方法:

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
        
    

在部分标题中定义按钮(我必须将 toggleSelection 参数 _: 替换为 sender: 以使其对我有用:

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函数:

@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)
            
        
    

插入或删除部分:

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
    

再次向pbasdf致敬

【讨论】:

以上是关于展开/折叠带有支持 NSFetchedResultsController 的 UITableView 部分的主要内容,如果未能解决你的问题,请参考以下文章

带有超链接和 1 单击展开/折叠的 Google 组织结构图

带有展开和折叠功能的 Android 视频视图

ASP.NET 使用带有展开/折叠功能的嵌套、可编辑 GridView 的 JQuery

快速展开/折叠单元格按钮单击

如何使 HTML 可折叠按钮默认为展开?

是否可以在电子邮件中折叠/展开 DIV?哪些客户支持这一点?