使用 NSFetchedResultsController 如何以编程方式设置 5 个部分以显示以及过滤它们以获取适当的行
Posted
技术标签:
【中文标题】使用 NSFetchedResultsController 如何以编程方式设置 5 个部分以显示以及过滤它们以获取适当的行【英文标题】:Using NSFetchedResultsController how can I programmatically set 5 sections to show up as well as filter them to get the appropriate rows 【发布时间】:2016-05-29 20:51:55 【问题描述】:在这里重新迭代是我的困境,我正在重构我的代码以使用 NSFetchedResultsController,我希望对一组 5 个部分进行硬编码,无论它们是否有任何行或不是。我在让它们出现方面取得了一定的成功,但不幸的是,我无法让适当的行出现在正确的部分下。 (注意:我正在使用带有按钮的自定义标题,当用户想要将单元格添加到任何特定部分时,该按钮会添加行。) coreData 中的模型关系是 Dog 模型可以有许多 Task,但 Task 只能有一只狗。不幸的是,我似乎正在为我创建的每一个独特的“狗”获取所有任务,因此它们都有相同的信息。
这是我的任务模型和我用来在 NSFetchedResultsController 中创建 5 个部分的枚举。
import Foundation
import CoreData
enum Type: String
case Meals = "Meals"
case Exercise = "Exercise"
case Health = "Health"
case Training = "Training"
case Misc = "Misc"
class Task: NSManagedObject
static let kClassName = "Task"
convenience init?(title: String, type: Type, isComplete: Bool, context: NSManagedObjectContext = Stack.sharedStack.managedObjectContext)
guard let entity = NSEntityDescription.entityForName(Task.kClassName, inManagedObjectContext: context) else return nil
self.init(entity: entity, insertIntoManagedObjectContext: context)
self.title = title
self.isChecked = isComplete
self.type = type.rawValue
这是我的 FetchedResultsController,我将枚举类型作为我的 sectionNameKeyPath 传递。
class TaskController
static let sharedController = TaskController()
private let kTask = "Task"
var fetchedResultsController: NSFetchedResultsController
var dog: Dog?
init()
let request = NSFetchRequest(entityName: kTask)
let sortDescriptor1 = NSSortDescriptor(key: "type", ascending: true)
request.sortDescriptors = [sortDescriptor1]
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: Stack.sharedStack.managedObjectContext, sectionNameKeyPath: String(Type), cacheName: nil)
_ = try? fetchedResultsController.performFetch()
在 numberOfRowsInSection 中,我试图将sections.count 匹配为等于函数属性部分的值,因为我假设 fetchedResultsController 部分在首先创建行之前实际上并不存在?老实说,我在这一点上迷路了,因为我不确定如何获取正确的行以匹配适当的部分。在我决定重构代码并将其更新为 NSFetchedResultsController 方式之前,注释代码是我最初检索正确部分的正确行的方式。
extension DogDetailViewController: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
guard let sections = TaskController.sharedController.fetchedResultsController.sections else return 0
if sections.count > 0 && section < sections.count
return sections[section].numberOfObjects
return 0
//
// if let dog = self.dog
// switch section
// case 0:
// return dog.tasks.filter($0.type == String(Type.Meals)).count
// case 1:
// return dog.tasks.filter($0.type == String(Type.Exercise)).count
// case 2:
// return dog.tasks.filter($0.type == String(Type.Health)).count
// case 3:
// return dog.tasks.filter($0.type == String(Type.Training)).count
// case 4:
// return dog.tasks.filter($0.type == String(Type.Misc)).count
// default:
// return 0
//
// else
// return 0
//
感谢任何帮助,谢谢!
【问题讨论】:
【参考方案1】:如果您希望所有 5 个部分都显示出来,无论给定的狗存在多少该类型的任务,您都可以考虑对部分的数量及其顺序进行硬编码,并使用 NSFetchedResultsController
(因此不同NSFetchRequest
) 每个部分。
您现有的提取请求不会将您的请求限制为特定的狗(您可以通过创建具有适当约束的NSPredicate
并将其设置在您的提取请求中来做到这一点)。这可能很重要,因此您不要将每个Task
都拉入内存然后执行过滤器。这将要求您的 Task
实体与数据模型中的 Dog
实体有关系,根据您发布的代码,这并不明显。我会假设你有这种关系。您也不能像现在这样为您的TaskController
(下面的TaskDataSource
)使用单例模型,因为每次更改您想要制作的Dog
时都必须创建一个新模型请求。
还请注意,我只是将TaskController
变成了TaskDataSource
,并直接采用了UICollectionViewDataSource
协议。这不是必需的,但如果您的应用可能希望在多个屏幕上显示此内容,则它可能更易于重复使用。
class TaskDataSource: NSObject, UICollectionViewDataSource
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
return TaskDataSource.Sections.count
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
let count: Int
switch TaskDataSource.Sections[section]
case .Meals:
count = mealsResultsController?.sections?.first?.numberOfObjects ?? 0
case .Exercise:
count = exerciseResultsController?.sections?.first?.numberOfObjects ?? 0
case .Health:
count = healthResultsController?.sections?.first?.numberOfObjects ?? 0
case .Training:
count = trainingResultsController?.sections?.first?.numberOfObjects ?? 0
case .Misc:
count = miscResultsController?.sections?.first?.numberOfObjects ?? 0
return count
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
let task: Task
// Each fetched results controller has a singel section, so we have to make an appropriate index path
let adjustedIndexPath = NSIndexPath(forItem: indexPath.item, inSection: 0)
switch TaskDataSource.Sections[indexPath.section]
case .Meals:
task = mealsResultsController?.objectAtIndexPath(adjustedIndexPath) as! Task
case .Exercise:
task = exerciseResultsController?.objectAtIndexPath(adjustedIndexPath) as! Task
case .Health:
task = healthResultsController?.objectAtIndexPath(adjustedIndexPath) as! Task
case .Training:
task = trainingResultsController?.objectAtIndexPath(adjustedIndexPath) as! Task
case .Misc:
task = miscResultsController?.objectAtIndexPath(adjustedIndexPath) as! Task
// This part will vary, depending on your cell / storyboard, but this is the idea. Note we don't use the adjusted index path here
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TaskCell", forIndexPath: indexPath) as! TaskCell
cell.titleLabel.text = task.title
return cell
init(dog: Dog)
// Create a sort descriptor to sort by whatever you like, I assume you'd want things sorted by title
let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
// A closure to create an NSFetchedResultsController, this avoids copy/pasting
let createFetchRequestForType = (type: Type) -> NSFetchedResultsController? in
let fetchRequest = NSFetchRequest(entityName: Task.kClassName)
// Note, you'll want to create a multi-key index on the Task entity to make sure this is reasonably fast
fetchRequest.predicate = NSPredicate(format: "dog == %@ && type == %@", dog, type.rawValue)
fetchRequest.sortDescriptors = [sortDescriptor]
let context = Stack.sharedStack.managedObjectContext
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
do
try fetchedResultsController.performFetch()
catch
return nil
return fetchedResultsController
mealsResultsController = createFetchRequestForType(.Meals)
exerciseResultsController = createFetchRequestForType(.Exercise)
healthResultsController = createFetchRequestForType(.Health)
trainingResultsController = createFetchRequestForType(.Training)
miscResultsController = createFetchRequestForType(.Misc)
static let Sections: Array<Type> = [.Meals, .Exercise, .Health, .Training, .Misc]
var mealsResultsController: NSFetchedResultsController?
var exerciseResultsController: NSFetchedResultsController?
var healthResultsController: NSFetchedResultsController?
var trainingResultsController: NSFetchedResultsController?
var miscResultsController: NSFetchedResultsController?
【讨论】:
谢谢@Charles A.,今晚晚些时候再试一试。有趣的是,您为 Sections 数组中的每个项目分配了自己的 NSFetchedResultsController 变量分配。我只是假设我只需要一个主要的 NSFetchedResultsController 来过滤我想要的信息? 如果您想在特定类型的任务不存在时完全省略该部分,我会使用一个NSFetchedResultsController
,在NSPredicate
中省略对type
的约束,而是拆分使用sectionNameKeyPath
将其分成几个部分。不过,鉴于您总是想要这些部分,我觉得这是一种更简单的方法。理想情况下,您希望在合理的情况下使用谓词在数据库引擎中进行过滤,而不是在内存中。以上是关于使用 NSFetchedResultsController 如何以编程方式设置 5 个部分以显示以及过滤它们以获取适当的行的主要内容,如果未能解决你的问题,请参考以下文章
在 Swift 3 中难以配置 NSFetchedResultsController