Post-fetch sorting / Workaround for NSSortDescriptor + NSFetchRequest / Sorting a tableView - ios

My quest to collect the scattered, magical pieces of code needed to create an animated and dynamically updating UITableView - artifacts hidden in the dark and ghastly depths of Apple Inc's feared dungeon, The Documentation - has with the help of the ever-friendly townsfolk and everyday heroes of Stack Overflow finally been completed.
But worry not, the end of this quest is but the beginning of a new one.
I have one UITableView. That tableView is hooked up to a NSFetchedResultsController. The FRC delegate methods are all up and running as pr. Apple's example code.
I have two NSManagedObjectContexts:
The Truth. This MOC is only inserted to / deleted from when the user adds or deletes an object.
The scratch pad. This is the MOC that the tableView's FRC is hooked up to. Any change here is reflected in the tableView with nice animations.
The scratch pad is seeded with objects from The Truth, but it is never saved. This means that I can insert and delete (show and hide) objects here to my heart's content, all while the tableView politely updates.
(To anyone reading this in an attempt to implement something like this I would say: get acquainted with [managedObjectContext objectWithID:id])
My question comes from the need to sort the tableView. As made clear to me by reading about NSSortDescriptor in conjunction with NSFetchRequest, using a sortDescriptor simply won't fly when one's using a SQLite store. The Docs say, "instead you should sort the returned array in memory". All-right then! But how do I go about doing that?
Where, in my logic as described above, do I inject this sorted array? Wherever I turn, there seems to be problems.

Related

iOS: Design pattern for populating asynchronously fetched data

I am developing an app that fetches data from the web and displays it to the user. Assume that the data is reviews of a restaurant and one review is displayed on one view. The user can swipe left or right to go to the prev/next review. The data is fetched asynchronously (one thread for each review).
Here is the problem statement - Assume that 5 reviews have been fetched and the user is looking at the 3rd one currently. Now, the 6th review is fetched and I want to display it as the 4th review to the user (because the publish date of the 6th review is more recent than the 5th review). How should my model class inform the view controller?
I have considered some options -
Provide an array to the view controller and then send NSNotifications about new items to be inserted in-between the array at a specific index
Use an NSFetchedResultsController (this is a bit tricky because I am not using it with a table view controller)
View controller always asks for the next review to be displayed (from the model) and does not have a array of reviews with it
Are there any established design patterns that are employed in such a scenario? Other suggestions apart from the 3 above are welcome!
Just use an NSFetchedResultsController. When using NSIndexPaths just ignore the section. It's basically a glorified NSArray with free notifications.
Here's how I think I'd do it:
Make sure that the NSFetchRequest for your NSFetchedResultsController is sorted by publish date.
Handle NSFetchedResultsControllerDelegate methods.
When the NSFetchedResultsController updates, save the current object, reload the collection view, and then scroll to the saved object without any animation. This will appear to the user as if nothing happened to the current page.
While there is no perfect design pattern for every programming problem, the closest I can think of that relates to your problem is a combination of the Command and Observer patterns.
https://en.wikipedia.org/wiki/Command_pattern
The observer pattern is used in the NSNotification center.
While it's unclear as to why you'd want to skip a review, you could have two arrays to store them when fetched. The first holds all reviews that you have fetched. The second holds all reviews that are displayed.
Then you can get the last review in the fetched array, as if it were a stack. This way you always have the last one loaded displayed to the user.
I am confused why the order of display is different than the true order, ie why the 6th review comes before the 5th, but you asked about patterns to help.
Apart from MVC and observer, which are in the other answers and comments, I'd suggest using lazy loading with a virtual proxy. When reviews have been fetched, you can just display their proxy (eg with a "loading..." Message until they're fully in memory).
See more here: http://en.wikipedia.org/wiki/Proxy_pattern
I would recommend using the observing pattern to inform your controller than new data as been fetched. When receiving the signal, your view controller could update its array of "restaurant review" (either by adding the old one and reordering it according to some sort descriptors of your flavor or by querying the DAO directly).
Let's say you are fetching your data from internet and populating a CoreData entity with the results. Once you got your downloaded data you can populate your core data "Review" entity.
In order to "listen" at the change happening in core data, your controller should, in the viewDidLoad body, register itself as an observer for the NSManagedObjectContextDidSaveNotification.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(updateInfo:) name:NSManagedObjectContextDidSaveNotification object:nil];
Then in your updateInfo, you can get the changes
- (void) updateInfo:(NSNotification *)notification
{
self.reviews = [self.managedObjectContext performRequest:myFetchRequest error:nil];
}

Why Paul Hegarty says not to change NSFetchedResultsController #properties?

Overview:
Stanford iOS tutorials contains an implementation (header + implementation file) to help use the table view while using core data.
Link - http://www.stanford.edu/class/cs193p/cgi-bin/drupal/downloads-2011-fall
File Name - CoreDataTableViewController.zip
CoreDataTableViewController.h contains the following text:
// Remember that once you create an NSFetchedResultsController, you CANNOT modify its #propertys. If you want new fetch parameters (predicate, sorting, etc.), create a NEW NSFetchedResultsController and set this class's fetchedResultsController #property again.
Question
It sounds a bit strange, because in docs they mention situation when you change properties, and recommend some thing in this case... Look like some mistake, or I miss something. If i just change predicate and fetch again, everything works...
It depends if you are using a cache or not. As it says in the documentation you link to:
If you are using a cache, you must call deleteCacheWithName: before changing any of the fetch request, its predicate, or its sort descriptors. You must not reuse the same fetched results controller for multiple queries unless you set the cacheName to nil.
If you are changing these properties, then it is probably simpler (this is a beginner's course, and the creation of the fetched results controller is done outside of this sample code) to just tell you to create a new fetched results controller than to go into an explanation of caching, and clearing a cache, and so on.

iOS: How do I add/remove a NSPredicate to my FetchedResultsController from a button press?

I have a button that I currently have set up as a toggle. It currently only changes it's text when pressed. I am wanting to have it filter out entries in my uitableview though. I'm guessing I'd do that by adding a NSPredicate to my fetchedresultscontroller, but I'm unsure how to do this. Anyone know how I might be able to do this or send me in the correct direction if this is not the proper way of doing this task? Thanks!
On button click, you would be require to release the instance of fetchedresultscontroller (if in memory), as you would be require to re-create FetchRequest with Predicate in it.
After that, you could call execute api. As, fetchedresultscontroller will be required a new predicate.
Preferrable is to use 1 level of fetchedResultsController api call from Core Data. Because Fetching from DB is costly as compared to filtering the objects w.r.t already loaded objects in memory.
In your case, use fetchedresultscontroller with fetching all required apis, and as per toggle, just use predicate on in-memory array/records. This will improve performance and secondly, since the records will be core-data managed objects, so if they will be in no-more use, they could get faulted.

NSFetchedResultsControllerDelegate vs NSFetchedResultsController

After reading lots of documentation and examples of Core Data framework, I still don't quite get it. What happens is that I sort of understand parts of a sample code or documentation, but often can't fit it into a larger picture. The second time I read the same code, I still need lots of time to figure it out just like the first time. It's so frustrating.
The NSFetchedResultsControllerDelegate vs NSFetchedResultsController is one of those concepts in Core Data that confuse me.
I think what I need is a simple and conceptual explanation, maybe analogy will be helpful.
All your core data objects have to be accessed via managed object context.
Think of NSFetchRequest as a representation of the database query. When you tell the NSManagedObjectContext (MOC) to fetch data, you give it a fetch request, so it knows what to fetch.
Now, let's say you have a table view. You make a fetch, and have all the data, even for the stuff you don't need to display. Each time the table view changes, and you need to refetch the data. There are a few other issues with just using a fetch request, though it can be done.
To solve some of these problems, enter NSFetchedResultsController (FRC). It manages the database so that you only have the objects in memory that are actually needed at the time. Furthermore, it hooks into the MOC so that when the database changes, it automatically changes its own data.
So, you create a FRC and give it a fetch request. Now, it manages the data so it only has in memory what you want. But, you need to tell it what to grab, and it need to tell you when it has data.
Thus is where the NSFetchedResultsControllerDelegate comes in.
The delegate is the glue between the table view (or some other component) and the FRC. The delegate methods are the communication channel that informs the FRC what data to get, when to get it, then hand it off to the table view.
EDIT
Yes, FRC manages the actual data part. However, when its data changes, it has to have some way to notify the guy who is watching the data. That's what the delegate is for. Let's take a different attack by looking at the delegate methods.
Imagine that the database has been changed in some way. The FRC, through its special magical incantations, has noticed the change, and, like a teenager with a new iPhone, needs to tell someone.
Specifically, again, in your case, it needs to tell the table view that is responsible for displaying the data to the user. Well, how is it going to tell the table view that the data has changed? There are actually several patterns used in iOS, and in this case, we use a delegate.
The guy who is interested in receiving this information from the FRC give him a pointer to an object that implements the delegate methods. When the FRC wants to notify the guy interested, it calls the appropriate methods on the object that is was given as the delegate.
Consider a change has happened. The FRC code would look something like this (ultra-simplified but to give the algorithmic idea).
[delegate controllerWillChangeContent:self];
// Process all the changes...
for (SectionChangeInfo *info in changedSections) {
[delegate controller:self didChangeSection:info.sectionInfo atIndex:info.index forChangeType:info.changeType];
}
for (ObjectChangeInfo *info in changedObjects) {
[delegate controller:self didChangeObject:info.object atIndexPath:info.indexPath forChangeType:info.changeType newIndexPath:index.newIndexPath];
}
[delegate controllerDidChangeContent:self];
Thus, the FRC can tell "somebody" about changes when they occur. In your case, when you give the FRC a delegate, it will call those methods, thus giving you a chance to handle the changes when they happen.
The other delegate method is called to ask the delegate what it wants to use as a section title. So, assume the FRC needs to know what to use, it will call...
NSString *sectionTitle = [[section substringToIndex:1] uppercase];
if ([delegate respondsToSelector:#selector(controller:sectionIndexTitleForSectionName:)]) {
sectionTitle = [delegate controller:self sectionIndexTitleForSectionName:section];
}

Update to NSManagedObject causes NSFetchedResultsController delete

I have quite a frustrating problem that I have been struggling with for quite some time.
To provide some context and detail I have an iOS UISplitViewController application - standard master / detail stuff. The master view is a UITableView backed with an NSFetchedResultsController (which loads NSManagedObjects from a SQLite data store).
What seems to be happening is that any update within the details view (which can routinely cause updates to the 'master records' and are flushed to NSManagedObject's and ultimately the SQL data store) causes a DELETE operation on the NSFetchedResultsController.
I assume that this is because the write to the NSManagedObject property(s) are causing a fault of some kind, which in turn causes the NSFetchedResultsController to expunge it from it's cached result set. The end result is that records go 'missing' from the master view (e.g.: UITableCellView's are removed from the master UITableView).
The issue is that I don't want this to happen and I have no idea how to stop it...
Has anyone experienced this issue before and could possibly provide some guidance?
Thanks in advance,
Ben
I'm not sure if this answers your question, but I figured out the solution to a similar problem I was having. I was sorting the objects in my Core Data-backed UITableView by the first letter of the name of each object. In whatever tutorial I read, it told me to put a transient property 'NameInitial' in the NSManagedObject subclass that I would populate with the first letter of the name of that object. I then used that property as my sectionNameKeyPath to sort the objects into the proper sections in my UITableView.
I had a button on each cell that updated a property of the object associated with that cell, and I properly received NSFetchedResultsChangeUpdate messages in my didChangeObject delegate function. HOWEVER, sometimes, cells would get deleted and I would receive the NSFetchedResultsChangeDelete message for no apparent reason.
Then I noticed that the cells that were getting deleted had (null) as the NameInitial property for their associated object. I had forgotten that the transient NameInitial is only stored in memory, and so is not necessarily maintained all the time. Once I manually repopulated the NameInitial property each time I updated a cell, the deleting stopped. So if you are using a transient property to help sort/section your UITableView, this might be your problem.
Hope this helps, and good luck!
-Rick

Resources