views:

579

answers:

2

I'm making a sectioned table with fetched results, but am having a hard time getting custom sections worked out.

Normally one would just have an attribute to sort by, and use sectionNameKeyPath: to generate the sections. But my sorting attribute is calculated on the fly, and I can't seem to get the fetchedResultsController to use it correctly...

Update: Using jbrennan's advice below, I'm really close to the intended functionality. I've added a Category to NSDate that returns a "days ago" number; putting that in here gives me sections based on those numbers:

NSFetchedResultsController *aFetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
    managedObjectContext:managedObjectContext
    sectionNameKeyPath:@"myDateAttribute.daysAgo"
    cacheName:@"Root"];

Here's where I'm stuck: I don't need them sorted by "days ago," I need them sorted via some calculations based on other attributes in the entity. So I can't just call that custom Category method, I need to call a method with arguments, like so:

[myDateAttribute sortingRoutine:thisObject.value]

Or something like that. I hope that makes some degree of sense. Thanks a ton if you can help :)

+3  A: 

I did something similar to this in a (soon to be) shipping iPhone app. My sections were divided up by dates like this: Yesterday, Today, Tomorrow, In The Future...

Anyway, the trick for me was adding a Category to NSDate to determine in which section my fetched object belonged.

My managed object had a property called dueDate, which was an NSDate. When configuring the fetched results controller, I used @"dueDate.relativeDate" as the section key path.

In the Category, -relativeDate was declared as returning an NSString and also as a readonly property (either of which might be sufficient, I didn't try without having both, but it doesn't hurt having both a method and property declaration). Then I simply implemented the method at it worked beautifully.

jbrennan
Okay, I think I'm with you there. What if my NSDates are actually in a separate entity, though? I've got `Tasks` as the main entities getting fetched, and `Resets` are related (-to-many). So each `Task` has a bunch of `Reset` dates, with the most recent being the important one here. What I tried was using `@"reset.specialDateMethod` as the sort key, but it doesn't like that...
Triz
It depends on what your `specialDateMethod` does. In that method, you could probably filter through your set of Resets with an NSPredicate to find the important one, and then use its `date` how you please. You might also want to cache the `Reset` because I imagine that would get expensive. Perhaps your `Task` could have a property like "mostRecentReset" or whatever is relevant.
jbrennan
More to the point: What is `sectionNameKeyPath:` expecting? Does it have to be a string, which corresponds to an attribute in the entity? I think this is where I'm confused.
Triz
Update: I think I'm close. But: I can only put a method that takes no arguments in `sectionNameKeyPath:`. So using `@"myAttribute.myMethod"` works fine, but I need to pass the method information about the object in question -- I need to be able to do something like `@"[myAttribute myMethod:var otherArg:otherVar]"` But it chokes on that.
Triz
Wow, this is an excellent solution. I needed a transient property on NSDate kind of the same way as you, but could not quite figure out the transient properties. Putting a category on NSDate did the trick and is cleaner, keeping my model small. Thanks.
Jaanus
+1  A: 

You can try the following.

Add a transient attribute to your core data model in the Task entity. Then implement the

- (void)awakeFromFetch

method in your Task NSManagedObject class. See its documentation. Within the method you are allowed to set a value for the transient property using the values of the other properties. Note that there are some restrictions on what you can do, but this is well explained in the documentation (most notably you can not modify relationships or pass arguments; however, if you can compute you transient property using only the values of the other properties/relationships it should be perfectly fine).

Once you have done this, you simply use the transient property as the attribute you pass to get back the sections.

unforgiven
Aha! This worked beautifully, thanks. Now the app crashes when saving new Tasks, but I'll work on that for a bit and post a separate question if I have to. :)
Triz
Your app should not crash when saving: transient properties are not stored, therefore this can not be the cause in any way.
unforgiven
It actually isn't crashing during the save process, but *after* it, when the view switches back to the sectioned table. Wasn't doing this before these changes, and I haven't done anything else, so I think it's related. Perhaps `awakeFromFetch:` isn't being called when the view reappears?
Triz
awakeFromFetch: is called once, just before fetching the objects. When you switch back to the table, the NSFetchedResultsController delegate methods are called if you implemented them.
unforgiven
So do I need to put the same stuff I put into `awakeFromFetch:` into one of those methods, too? Any idea which would be the most appropriate? (An aside: How do you find these things out? ie., how did you know when `awakeFromFetch:` is called? I can't seem to find any of that kind of information in the reference docs...)
Triz
No, you don't need to replicate the same code. Try commenting out the NSFetchedResultsChangeUpdate case in the following method: controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:an see if this makes any difference.The documentation says that awakeFromFetch is invoked automatically by the Core Data framework after the receiver has been fetched. Actually, this method is called BEFORE the data is loaded into the NSManagedObject that will be returned to you by the fetch request.
unforgiven