使用 NSFetchedResultsController 我如何以编程方式设置 5 个部分以显示并过滤它们以获得适当的行
Using NSFetchedResultsController how can I programmatically set 5 sections to show up as well as filter them to get the appropriate rows
至 re-iterate 这是我的困境,我正在重构我的代码以使用 NSFetchedResultsController,我希望对一组 5 个部分进行硬编码,无论是否有任何部分我都希望始终显示行与否。我在让它们出现方面有些成功,但不幸的是我无法让适当的行显示在正确的部分下。 (注意:我使用的是带有按钮的自定义 header,每当用户想要将单元格添加到任何特定部分时,该按钮都会添加行。)coreData 中的模型关系是 Dog 模型可以有很多任务,但是任务只能有一只狗。不幸的是,我似乎正在为我创建的每个唯一 "Dog" 提取所有任务,因此它们都具有相同的信息。
这是我的任务模型和我用来在 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({[=12=].type == String(Type.Meals)}).count
// case 1:
// return dog.tasks.filter({[=12=].type == String(Type.Exercise)}).count
// case 2:
// return dog.tasks.filter({[=12=].type == String(Type.Health)}).count
// case 3:
// return dog.tasks.filter({[=12=].type == String(Type.Training)}).count
// case 4:
// return dog.tasks.filter({[=12=].type == String(Type.Misc)}).count
// default:
// return 0
// }
// } else {
// return 0
// }
}
感谢任何帮助,谢谢!
如果您希望显示所有 5 个部分,无论给定狗存在多少该类型的任务,您都可以考虑对部分数量及其顺序进行硬编码,并使用 NSFetchedResultsController
(因此每个部分都有不同的 NSFetchRequest
)。
您现有的获取请求并未将您的请求限制为特定的狗(您可以通过创建一个具有适当约束的 NSPredicate
并将其设置在您的获取请求中来实现)。这可能很重要,因此您不会将每个 Task
都拉入内存,然后执行过滤器。这将要求您的 Task
实体与数据模型中的 Dog
实体有关系,根据您发布的代码,这并不明显。我会假设你有这种关系。您也不能像现在这样为 TaskController
(下面的 TaskDataSource
)使用单例模型,因为每次更改 [=16= 时都必须创建一个新模型] 你想提出一个请求。
另请注意,我只是继续将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?
}
至 re-iterate 这是我的困境,我正在重构我的代码以使用 NSFetchedResultsController,我希望对一组 5 个部分进行硬编码,无论是否有任何部分我都希望始终显示行与否。我在让它们出现方面有些成功,但不幸的是我无法让适当的行显示在正确的部分下。 (注意:我使用的是带有按钮的自定义 header,每当用户想要将单元格添加到任何特定部分时,该按钮都会添加行。)coreData 中的模型关系是 Dog 模型可以有很多任务,但是任务只能有一只狗。不幸的是,我似乎正在为我创建的每个唯一 "Dog" 提取所有任务,因此它们都具有相同的信息。
这是我的任务模型和我用来在 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({[=12=].type == String(Type.Meals)}).count
// case 1:
// return dog.tasks.filter({[=12=].type == String(Type.Exercise)}).count
// case 2:
// return dog.tasks.filter({[=12=].type == String(Type.Health)}).count
// case 3:
// return dog.tasks.filter({[=12=].type == String(Type.Training)}).count
// case 4:
// return dog.tasks.filter({[=12=].type == String(Type.Misc)}).count
// default:
// return 0
// }
// } else {
// return 0
// }
}
感谢任何帮助,谢谢!
如果您希望显示所有 5 个部分,无论给定狗存在多少该类型的任务,您都可以考虑对部分数量及其顺序进行硬编码,并使用 NSFetchedResultsController
(因此每个部分都有不同的 NSFetchRequest
)。
您现有的获取请求并未将您的请求限制为特定的狗(您可以通过创建一个具有适当约束的 NSPredicate
并将其设置在您的获取请求中来实现)。这可能很重要,因此您不会将每个 Task
都拉入内存,然后执行过滤器。这将要求您的 Task
实体与数据模型中的 Dog
实体有关系,根据您发布的代码,这并不明显。我会假设你有这种关系。您也不能像现在这样为 TaskController
(下面的 TaskDataSource
)使用单例模型,因为每次更改 [=16= 时都必须创建一个新模型] 你想提出一个请求。
另请注意,我只是继续将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?
}