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
Allow Users to Send Messages to Multiple Users Simultaneously in a Messaging App
Why Does It Take Such a Long Time for UI to Be Updated from Background Thread
How to Convert Base64 into Nsdata in Swift
How to Update a Swiftui View State from Outside (Uiviewcontroller for Example)
Scrolling in Uicollectionview Selects Wrongs Cells - Swift
Adding Firebase Data to an Array in iOS Swift
Ios11 Uibarbuttonitem Not Working
My Reachability Notifier Is Only Able to Be Called Once
How to Authorize Twitter with Swifter
How to Detect Which Image Has Been Tapped in Swift
iOS Swift Remove Uitableview Cell Separator Space
Show More Button Next to End of Text Swift
Can't Change Uinavigationbar Prompt Color
Wkwebview: How to Preload Multiple Urls
Scaling Current Dot of Uipagecontrol and Keeping It Centered
Change Buttonstyle Modifier Based on Light or Dark Mode in Swiftui