I never imagined it'd be so hard to reorder a table and save the new order to Core Data (and perhaps I'm overthinking it). The bit of code below is throwing me the following error: "The number of rows contained in an existing section after the update must be equal to the number of rows contained in that section before the update."
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
initializeFetchedResultsController()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context2: NSManagedObjectContext = appDel.managedObjectContext
let request2 = NSFetchRequest(entityName: "Activities")
let activityOrderSort = NSSortDescriptor(key: "activityOrder", ascending: true)
request2.sortDescriptors = [activityOrderSort]
let predicate = NSPredicate(format: "date == %#", date)
request2.predicate = predicate
var fetchResults2: [NSManagedObject]
do {
try fetchResults2 = (appDel.managedObjectContext.executeFetchRequest(request2) as! [NSManagedObject])
if fromIndexPath.row > toIndexPath.row {
for i in toIndexPath.row..<fromIndexPath.row {
fetchResults2[i].setValue(i+1, forKey: "activityOrder")
}
fetchResults2[fromIndexPath.row].setValue(toIndexPath.row, forKey: "activityOrder")
}
if fromIndexPath.row < toIndexPath.row {
for i in fromIndexPath.row + 1...toIndexPath.row {
fetchResults2[i].setValue(i-1, forKey: "activityOrder")
}
fetchResults2[fromIndexPath.row].setValue(toIndexPath.row, forKey: "activityOrder")
}
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
do {
try appDel.managedObjectContext.save()
} catch let error as NSError {
print("Saving error: \(error.localizedDescription)")
}
initializeFetchedResultsController()
}
Here is the initializeFetchedResultsController() code for reference:
func initializeFetchedResultsController() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
context = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Activities")
let orderSort = NSSortDescriptor(key: "activityOrder", ascending: true)
fetchRequest.sortDescriptors = [orderSort]
let predicate = NSPredicate(format: "date == %#", date)
fetchRequest.predicate = predicate
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: self.context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
I tried to pull as much as I could from the answer from a previous post here: Save new order to core data after the using the tableView:moveRowAtIndexPath:toIndexPath: method. Also, I have the canEditRowAtIndexPath function set up and it appears to be functioning properly. Is there anything obvious that I'm messing up in the provided code? Is there an easier solution? Thanks for the support.
Related
I have a many to many relationship in Core Data with posts and tags. Each tag has many posts and each post has many tags. I have a view controller where I want to display, for a particular tag, all of the posts associated with it.
I do this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
//2
let fetchRequest: NSFetchRequest<Tag> = Tag.fetchRequest()
//3
fetchRequest.predicate = NSPredicate(format: "Tag.name == %#", tag.name!)
// let sort = NSSortDescriptor(key: "timeStamp", ascending: true)
// fetchRequest.sortDescriptors = [sort]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil) as? NSFetchedResultsController<Tag>
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
tag = fetchedResultsController.object(at: IndexPath(item: 0, section: 0))
tag = Tag(context: managedContext)
let posts = tag.posts
My posts object at the bottom is a Set of objects - unordered. I want an array of posts ordered by timestamp that all belong to this particular tag and Im not sure how to create that.
I know usually to sort items by timestamp you would introduce an NSSortDescriptor like I did in step 3 (but is commented out). But I believe this would sort my fetch request (Tag) by the Tag's timestamp. Each tag doesn't have a timestamp and what I really want to sort is the posts associated with that tag. So I don't believe this is the way to do this.
How do I go about this? I think I could just use swift but it feels like there must be a Core Data way to sort related objects by timestamp
Edit: Updated approach with Paulw11's advice:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
//2
let tagFetchRequest: NSFetchRequest<Tag> = Tag.fetchRequest()
let postFetchRequest: NSFetchRequest<Post> = Post.fetchRequest()
//3
tagFetchRequest.predicate = NSPredicate(format: "%K == %#", #keyPath(Tag.name), tag.name!)
do {
let results = try managedContext.fetch(tagFetchRequest)
tag = results.first
} catch let error as NSError {
print("Tag Fetch error: \(error) description: \(error.userInfo)")
}
guard let tag = tag else { return }
postFetchRequest.predicate = NSPredicate(format: "%# IN Post.tags", tag)
let sort = NSSortDescriptor(key: "timeStamp", ascending: true)
postFetchRequest.sortDescriptors = [sort]
fetchedResultsController = NSFetchedResultsController(fetchRequest: postFetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
posts = fetchedResultsController.fetchedObjects as! [Post]
firstPostDate = posts.first?.timeStamp as Date?
lastPostDate = posts.last?.timeStamp as Date?
for (i,post) in posts.enumerated() {
let date = post.timeStamp as Date?
let day = date?.days(from: Calendar.current.startOfDay(for: firstPostDate!))
indexMap[day!] = IndexPath(row: i, section: 0)
}
This gives this error message:
2018-11-29 15:36:56.261887-0800 SweatNetOffline[31250:17419187] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (<Tag: 0x60000216b2a0> (entity: Tag; id: 0xc36a752f5b05e34c <x-coredata://F02C5E33-89B9-4814-9D1B-8C74CAEC7DA1/Tag/p79> ; data: {
mostRecentThumbnail = nil;
mostRecentUpdate = "2018-12-07 21:47:44 +0000";
name = test;
posts = (
"0xc36a752f5af9e34e <x-coredata://F02C5E33-89B9-4814-9D1B-8C74CAEC7DA1/Post/p48>"
);
uuid = nil;
}) IN Post.tags)'
Looks like its inserting that entire object into the query string. This seems wrong but maybe I need to format the object differently?
The key was to use request.predicate = NSPredicate(format: "%# IN self.tags", tag)
as opposed to request.predicate = NSPredicate(format: "%# IN Post.tags", tag)
The context of the predicate is the Post so it no longer needs to be accessed I suppose, it is assumed. Self.tags, tags, and SELF.tags all work.
I'm trying to add a SearchBar to a TableView, which is storing its data with CoreData. But when I try to search the TableView isn't updating. I think that the fetch is working, but table is not being updated.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
return
}
else {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let sRequest: NSFetchRequest<Note> = Note.fetchRequest()
sRequest.predicate = NSPredicate(format: "SELF.title CONTAINS[cd] %#", searchText)
sRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: sRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
print(sRequest)
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to fetch entities: \(error)")
}
self.mainTable.reloadData()
}
return
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = mainTable.dequeueReusableCell(withIdentifier: "prototypeCell")!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let tableRequest: NSFetchRequest<Note> = Note.fetchRequest()
tableRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
var fetchedResultsController = NSFetchedResultsController(fetchRequest: tableRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to fetch entities: \(error)")
}
let noteCell = fetchedResultsController.object(at: indexPath)
cell.textLabel?.text = noteCell.title!
let formatter = DateFormatter()
formatter.dateFormat = "d.M.y, HH:mm"
let dateString = formatter.string(from: noteCell.date! as Date)
cell.detailTextLabel?.text = dateString
return cell
}
The problem is this line:
let fetchedResultsController = NSFetchedResultsController(...
let means that this is a local variable. So you create a new fetched results controller and then throw it away.
Meanwhile, in your cellForRowAt, you refer to something else called fetchedResultsController:
var fetchedResultsController = ...
These are two completely different objects.
The fact is that both are wrong. You should not be doing a fetch every time your table view data source is called upon to consider a row. Look at how the Xcode app templates work! There is one fetched results controller, permanently. It does its fetch once and then just sits there holding the data. That is what you should be doing.
I'm refactoring an existing project from Swift 2 to Swift 3. Everything has been straightforward until I got to refactoring Core Data. I'm able to create managed objects and persist them in the managedObjectContext, but I'm having difficulty getting NSFetchedResultsController to work. I took a look at this post, but it's not getting me across the finish line.
After importing records from a JSON, I verify there are objects in my managedObjectContext with the following code:
func recordCount() -> Int {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "MyEntity")
let count = try! context.count(for: fetchRequest)
return count
}
When I create a fetchedResultsController, I'm running into trouble. My code doesn't crash, but it doesn't return NSManagedObjects despite there being objects that match my search.
Here's how I'm creating my NSFetchedResultsController in a UIViewController.
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
// This is set on a prior viewController before segue.
// I've verified it's not nil
var selectedEquipmentString: String?
let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
lazy var fetchedResultsController: NSFetchedResultsController<MyEntity> = {
// I've tried altering the syntax of the fetchRequest
// let fetchRequest: NSFetchRequest<MyEntity> = MyEntity.fetchRequest()
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "MyEntity")
let sortDescriptor = NSSortDescriptor(key: "generalArea", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = NSPredicate(format: "equipmentDescription == %#", self.selectedEquipmentString!)
let frc: NSFetchedResultsController<MyEntity> = NSFetchedResultsController(fetchRequest: fetchRequest as! NSFetchRequest<MyEntity>, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: "generalArea", cacheName: nil)
frc.delegate = self
return frc
}()
// MARK: - View Lifecycle Methods (abbreviated)
override func viewDidLoad() {
super.viewDidLoad()
// I've tried moving this call to viewWillAppear and viewDidAppear without success
fetchObjectsFromManagedObjectContext()
}
// MARK: - Core Data Methods (abbreviated)
func fetchObjectsFromManagedObjectContext() {
do {
try fetchedResultsController.performFetch()
} catch {
print("error: \(error)")
return
}
print ("There are \(fetchedResultsController.fetchedObjects!.count) returned from fetchObjectsFromManagedObjectContext")
}
}
This code doesn't crash, but it doesn't return any records from a fetchRequest. I was able to force a crash with a typo in the predicate, but without a typo there are no objects returned despite objects that match the predicate.
I welcome any suggestions re: where my mistake is. I rest assured knowing it will be a startlingly silly oversight on my part. Thank you for reading.
Your NSFetchRequest should have a type NSFetchRequest<MyEntity>, but you specify NSFetchRequest<NSFetchRequestResult>. Try changing this and let me know if it helps or not
please check below code for NSFetchedResultsController swift 3..
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
// MARK: - NSFetchedResultsController
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<UserExistenceOnXMPPCD> = {
let fetchRequest = NSFetchRequest<UserExistenceOnXMPPCD>(entityName: "UserExistenceOnXMPPCD")
fetchRequest.sortDescriptors = [
NSSortDescriptor(key: "name", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext:CoreDataController.sharedInstance.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
try! fetchedResultsController.performFetch()
fetchedResultsController.delegate = self
if let quotes = fetchedResultsController.fetchedObjects {
if quotes.count > 0 {
print(quotes.count)
}
}
return fetchedResultsController
}()
// MARK: - NSFetchedResultsController delegate methods
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.reloadData()
}
I'm using the NSFetchedResultsController, to populate a UITableView, i'm trying to add a category filter, so if the user choose a category a need to reload the data on the UITableView, this is what i'm trying to do.
var categoriaAtual : Int?
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
print("Already fetch")
return _fetchedResultsController!
} else {
print("New Fetch")
}
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
self.managedObjectContext = managedContext
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Frases", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "favorita", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchLimit = 20
if categoriaAtual != nil {
print("Categoria Atual \(categoriaAtual)")
fetchRequest.predicate = NSPredicate(format: "categoria = %d",categoriaAtual!)
fetchRequest.fetchLimit = 2
} else {
print("No predicate");
}
//
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//print("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func setarCategoria(cat: Int) {
categoriaAtual = cat
_fetchedResultsController = nil
self.tableView.reloadData()
}
Now i have a slide out menu called MenuTableView.swift,i make the call like this: ( I Think the problem is here )
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeTableViewController
homeVC.setarCategoria(indexPath.row)
self.slideMenuController()?.closeLeft()
}
I do get the print saying that this is a new fetch, but the TableView does not change at all.
instantiateViewControllerWithIdentifer creates a new instance of the viewController (and therefore a new instance of the fetchedResultsController), which is how you get the "New Fetch" log but your original table view doesn't change. You probably just need to give MenuTableView a delegate that your HomeViewController can implement.
I am downloading information from the internet and using the data to create entities in Core Data. I am trying to sort the entities (The entities are TV Shows, the data is from Trakt) by the airDate attribute of a TVEpisode entity that has a relationship to the TVShow entity. The TVShow entity only has this relationship to the show if the show data has an episode that is airing at a future date from the current time.
So the way I want to sort the data is:
Top: Shows that have a upcomingEpisode relationship, sorted by the airDate attribute of the upcomingEpisode, ordered ascendingly.
Middle: Shows that have no upcomingEpisode relationship but will be returning.
Bottom: Shows that have no upcomingEpisode relationship and that are ended/cancelled
Here are the issues I am running into getting this to work.
Issue 1: Using 1 NSFetchedResultsController
let fetchRequest = NSFetchRequest(entityName: "TVShow")
let airDateSort = NSSortDescriptor(key: "upcomingEpisode.airDate", ascending: true)
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [airDateSort, titleSort];
upcomingShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: "upcomingShows")
upcomingShowsResultsController.delegate = self;
var error: NSError? = nil
if (!upcomingShowsResultsController.performFetch(&error)) {
println("Error: \(error?.localizedDescription)")
}
Using this NSFetchedResultsController will put all TVShow entities with no upcomingEpisode relationship on top, sorted all by title, I need the dead shows sorted by title on the very bottom and returning shows sorted by title in the middle.
Issue 2: Using multiple NSFetchedResultsController's
func setupUpcomingShowsFetchedResultsController() {
let fetchRequest = NSFetchRequest(entityName: "TVShow")
let airDateSort = NSSortDescriptor(key: "upcomingEpisode.airDate", ascending: true)
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [airDateSort, titleSort];
let predicate = NSPredicate(format: "upcomingEpisode != nil")
fetchRequest.predicate = predicate
upcomingShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: "upcomingShows")
upcomingShowsResultsController.delegate = self;
var error: NSError? = nil
if (!upcomingShowsResultsController.performFetch(&error)) {
println("Error: \(error?.localizedDescription)")
}
}
func setupReturningShowsFetchedResultsController() {
let fetchRequest = NSFetchRequest(entityName: "TVShow")
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [titleSort];
let predicate = NSPredicate(format: "status == 'returning series'")
let predicate2 = NSPredicate(format: "upcomingEpisode == nil")
fetchRequest.predicate = NSCompoundPredicate.andPredicateWithSubpredicates([predicate!, predicate2!])
returningShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
returningShowsResultsController.delegate = self;
var error: NSError? = nil
if (!returningShowsResultsController.performFetch(&error)) {
println("Error: \(error?.localizedDescription)")
}
}
func setupDeadShowsFetchedResultsController() {
let fetchRequest = NSFetchRequest(entityName: "TVShow")
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [titleSort]
let endedShowsPredicate = NSPredicate(format: "status == 'ended'")
fetchRequest.predicate = endedShowsPredicate
deadShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
deadShowsResultsController.delegate = self;
var deadShowsError: NSError? = nil
if (!deadShowsResultsController.performFetch(&deadShowsError)) {
println("Error: \(deadShowsError?.localizedDescription)")
}
}
These work for what I want, but only when the data is already downloaded and in Core Data. When the app first launches and downloads the data it crashes every time because the number of rows in a section are not the same as what the table is expecting. I did manipulate the index paths that the NSFetchedResultsControllerDelegate gives in the didChangeObject function, and I printed out index's that are being inserted. The count that I did in any section was equal to how many the table view says it was expecting but it throws an error every time. This is how I am handling the method for multiple NSFetchedResultsController's
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
let section = sectionOfFetchedResultsController(controller)
let indexPathsComputed = [NSIndexPath(forRow: indexPath?.row ?? 0, inSection: section)]
let newIndexPathsComputed = [NSIndexPath(forRow: newIndexPath?.row ?? 0, inSection: section)]
dispatch_async(dispatch_get_main_queue(), { () -> Void in
switch type {
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths(newIndexPathsComputed, withRowAnimation: .Automatic)
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths(indexPathsComputed, withRowAnimation: .Automatic)
case NSFetchedResultsChangeType.Move:
self.tableView.deleteRowsAtIndexPaths(indexPathsComputed, withRowAnimation: .Automatic)
self.tableView.insertRowsAtIndexPaths(newIndexPathsComputed, withRowAnimation: .Automatic)
case NSFetchedResultsChangeType.Update:
if let index = indexPathsComputed[0] {
if let cell = self.tableView.cellForRowAtIndexPath(index) as? ShowTableViewCell {
self.configureCell(cell, indexPath: index)
}
}
else {
println("No cell at index path")
}
}
})
}
If the crashes could be fixed, this would be the best way to achieve what I want to do.
Issue 3: Using multiple Array's
func reloadShowsArray() {
let fetchRequest = NSFetchRequest(entityName: "TVShow")
let airDateSort = NSSortDescriptor(key: "upcomingEpisode.airDate", ascending: true)
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [airDateSort, titleSort];
let predicate = NSPredicate(format: "upcomingEpisode != nil")
fetchRequest.predicate = predicate
var error: NSError?
showsArray = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [TVShow]
if let error = error {
println(error)
}
}
func reloadDeadShows() {
let fetchRequest = NSFetchRequest(entityName: "TVShow")
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [titleSort]
let endedShowsPredicate = NSPredicate(format: "status == 'ended'")
fetchRequest.predicate = endedShowsPredicate
var error: NSError?
deadShows = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [TVShow]
if let error = error {
println(error)
}
}
This solves the crashing and works after the data is downloaded and while the data is being downloaded. But when using this, I have to call self.tableView.reloadData() when the data is downloaded, and the entities just pop into the table view with no animation, and I really want the animations from insertRowsAtIndexPaths because it looks better and is a better experience. I tried calling reloadShowsArray() and then using the find() function with the entity to get the index so I could use insertRowsAtIndexPaths, but it returns nil every time for the index, even though the entity was saved with the context before that. Also the cells will not get automatically reloaded or moved around like with NSFetchedResultsController
So what is the best way to handle this, and how can I get the desired sorting with the animations?
As per comments, I suspect the three-FRC method causes problems because one FRC calls controller:didChangeContent (which triggers tableView.endUpdates) while another FRC is still processing updates. To overcome this, implement a counter which is incremented in controller:willChangeContent and decremented in controller:didChangeContent. The tableView beginUpdates should only be called if the counter is zero, and endUpdates only when the counter returns to zero. That way, the endUpdates will only be called when all three FRCs have completed processing their updates.
If possible, I would also avoid the dispatch_async, since it could result in the table updates occurring outside the beginUpdates/endUpdates cycle.