展开/折叠带有支持 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 组织结构图