使用 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?
}