【问题标题】:Expand/collapse UITableView sections with a backing NSFetchedResultsController展开/折叠带有支持 NSFetchedResultsController 的 UITableView 部分
【发布时间】: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:”前缀。

标签: ios uitableview core-data


【解决方案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致敬

    【讨论】:

      猜你喜欢
      • 2014-12-25
      • 2010-12-28
      • 1970-01-01
      • 2016-04-01
      • 1970-01-01
      • 2019-08-31
      • 2015-06-27
      • 2013-12-18
      • 1970-01-01
      相关资源
      最近更新 更多