Nsfetchedresultscontroller Swift Sections

NSFetchedResultsController swift sections

First, you need to sort your List objects by the done attribute, then the firstName:

let doneSortDescriptor = NSSortDescriptor(key: "done", ascending: true)
let sortDescriptor = NSSortDescriptor(key: "firstName", ascending: true)
fetchRequest.sortDescriptors = [doneSortDescriptor, sortDescriptor]

Then amend your Fetched Results Controller to get it to use the done attribute to define the sections:

frc = NSFetchedResultsController(fetchRequest: listFetchRequest(), managedObjectContext: context, sectionNameKeyPath: "done", cacheName: nil)

Finally, implement tableView:titleForHeaderInSection to set an appropriate title for each section.

Creating sections with NSFetchedResultsController, on the fly

If you want an FRC with sections, you have to add a sort descriptor to the fetch request, and that sort descriptor cannot be based on transient attributes.

See the documentation of initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:`:

If the controller generates sections, the first sort descriptor in
the array is used to group the objects into sections; its key must
either be the same as sectionNameKeyPath or the relative ordering
using its key must match that using sectionNameKeyPath.

and Fetch Predicates and Sort Descriptors in the "Core Data Programming Guide":

The SQL store, on the other hand, compiles the predicate and sort
descriptors to SQL and evaluates the result in the database itself.
This is done primarily for performance, but it means that evaluation
happens in a non-Cocoa environment, and so sort descriptors (or
predicates) that rely on Cocoa cannot work. The supported sort
selectors are ...

In addition you cannot sort on transient properties using the SQLite store.

This means that you cannot create sections purely on transient attributes. You need a persistent attribute that creates the ordering for the sections.

UPDATE: A typical use of a transient attribute as sectionNameKeyPath is: Your objects have a "timeStamp" attribute, and you want to group the objects into sections with one section per month (see the DateSectionTitles sample code from the iOS Developer Library). In this case you have

  • a persistent attribute "timeStamp",
  • use "timeStamp" as first sort descriptor for the fetch request,
  • a transient attribute "sectionIdentifier" which is used as sectionNameKeyPath. "sectionIdentifier" is calculated from "timeStamp" and returns a string representing the year and the month of the timestamp, e.g. "2013-01".

The first thing the FRC does is to sort all fetched objects according to the "timeStamp" attribute. Then the objects are grouped into sections according to the "sectionIdentifier" attribute.

So for a FRC to group the objects into sections you really need a persistent attribute. The easiest solution would be to add a persistent attribute "sectionNumber" to your entity, and use that for "sectionNameKeyPath" and for the first sort descriptor.

Updating sections with NSFetchedResultsController

You need to add a sort descriptor for isComplete. It should be the first sort descriptor, then have your date one.

Basically, the sort and the sections need to be compatible, and yours currently aren't.

NSFetchedResultsController with sections created by first letter of a string

I think I've got yet another option, this one uses a category on NSString...

@implementation NSString (FetchedGroupByString)
- (NSString *)stringGroupByFirstInitial {
if (!self.length || self.length == 1)
return self;
return [self substringToIndex:1];
}
@end

Now a little bit later on, while constructing your FRC:

- (NSFetchedResultsController *)newFRC {
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:awesomeRequest
managedObjectContext:coolManagedObjectContext
sectionNameKeyPath:@"lastName.stringGroupByFirstInitial"
cacheName:@"CoolCat"];
return frc;
}

This is now my favorite approach. Much cleaner/easier to implement. Moreover, you don't have to make any changes to your object model class to support it. This means that it'll work on any object model, provided the section name points to a property based on NSString

Using NSFetchedResultsController how can I programmatically set 5 sections to show up as well as filter them to get the appropriate rows

If you want all 5 sections to show up, regardless of how many tasks of that type exist for a given dog, you might consider hard coding the number of sections and their order and having an NSFetchedResultsController (and thus a different NSFetchRequest) for each section.

Your existing fetch request is not constraining your request to a specific dog (you would do this by creating an NSPredicate with the appropriate constraint and setting it on your fetch request). This is probably important to do, so you don't pull every Task into memory and then perform the filter. This would require that your Task entity has a relationship to the Dog entity in your data model, which isn't obvious based on the code you've posted. I will assume that you have this relationship. You also can't use a singleton model for your TaskController (TaskDataSource below) in the way you are now, as you'll have to make a new one every time you change the Dog you want to make a request for.

Note also that I just went ahead and made TaskController into TaskDataSource and adopted the UICollectionViewDataSource protocol directly. This isn't necessary, but makes this potentially more re-usable if your app might want this content on more than one screen.

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

Swift NSFetchedResultsController Sections Always returns nil

Answering my own question, I think I realized what's wrong, although I don't know why it is wrong and why it works with another Entity with TableView.

The problem seems to me the predicate. Commenting the line:

    request.predicate = nil

seems to solve the problem.



Related Topics



Leave a reply



Submit