2

I have an app multi-language. The user inserts data based on the current language he/she chooses, for example: if the user chooses English, then that data he/she is inserting at the moment will be linked with the language English, if the user decides to change language, for example, go for the Portuguese language and then insert new record, the current record will be linked with the Portuguese language. So far, so good!

My problem is: I have a tableView where is loaded a list with the records the user inserted filtering by language (English, Portuguese, Italian, French etc..), I'm using core data for that (fetchedResultsController), but the problem is, the list doesn't update AT ALL!!! It only updates if I close the app and open it again. For example: I open the app with the English Language selected, then everything goes fine, but if I decide to change language to Portuguese, for example, then I come back to my list view and the listView still brings the list for the English language.

Looks like is something related to fetchedResultsController cache's name. In the apple's API reference it says: "If you are using a cache, you must call deleteCache(withName:) 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" So I tried to delete the cache's name and create another one based on the current language, but no lucky! I've been stuck on the problems for 2 days and still didn't figure it out.

here is my fetchedResultsController

 lazy var fetchedResultsController: NSFetchedResultsController<TranslationContainer> = {

    let fetchRequest = NSFetchRequest<TranslationContainer>()

    let entity = TranslationContainer.entity()
    fetchRequest.entity = entity

    let predicate = NSPredicate(format: "ANY dictionary.language == %@", "Portuguese")// it just update if I close the app and open
    fetchRequest.predicate = predicate

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

    fetchRequest.fetchBatchSize = 20

    let fetchedResultsController = NSFetchedResultsController(
        fetchRequest: fetchRequest,
        managedObjectContext: self.managedObjectContext,
        sectionNameKeyPath: nil,
        cacheName: nil)// it's nil here now, but I tried to change the cache's name based on the current language, but it didn't work either
    fetchedResultsController.delegate = self
    return fetchedResultsController
}()
Rafael Paz
  • 497
  • 7
  • 22
  • Probably a duplicate of http://stackoverflow.com/questions/7533849/nsfetchedresultscontroller-with-relationship-not-updating. The FRC updates only on changes of the fetched object, not on changes of related objects. – Martin R Mar 06 '17 at 08:59
  • What does your core-data stack look like? are you merging changes correctly? – Jon Rose Mar 06 '17 at 09:04
  • @MartinR, thks, mate! – Rafael Paz Mar 06 '17 at 09:22

1 Answers1

1

A fetchedResultsController only monitors for changes for the entity it is fetching. So if you have a predicate based on a relationship it will do the initial fetch correctly, but will not update when a relationship updates. In your case, if a dictionary's language changes it will not update the fetchedResultsController because no TranslationContainer was changed.

One way to handle this is to modify a property on the TranslationContainer whenever you change a dictionary or languages. This will trigger the results controller to reevaluate the object.

Jon Rose
  • 8,373
  • 1
  • 30
  • 36
  • Tks for the insight, I didn't know I had to do this (I'm new to iOS), when I do this I'll be back to say if it did work! – Rafael Paz Mar 06 '17 at 09:22
  • sorry for my late reply, I've been stuck on my other job, just got time today to implement your solution. My question is: How could I change a property on TranslationContainer if core data takes control of my entire persistence? I tried, but don't know how to do this. Could you please provide more details? Where should I change the TranslationContainer object if I don't create manually one? Cheers mate, I really appreciate the help – Rafael Paz Mar 09 '17 at 07:37
  • just for the records... The problem is happening on my tableViewController. Which is responsible for just showing the list. My tableViewController doesn't create new TranslationContainer Objects, it just show them, so I don't know where and how to change a property on TranslationContainer. – Rafael Paz Mar 09 '17 at 07:56
  • Everywhere in your code that you change something that could effect the predicate of the fetchedResultsController you also need 'touch' the object that is being monitored. So if you have something like `dictionary.language = lang;` then in the line below it add something like `dictionary.translationContainer.containerId = dictionary.translationContainer.containerId` (just setting a property to itself will trigger a change). – Jon Rose Mar 09 '17 at 08:07
  • It didn't work, mate :( I did like this: In my DictionaryTableView where the user select the language, when the user clicks a cell to choose another language, I just retrieved from database (using core data) the last TranslationContainer inserted, then I got one property of it, pass to a temporary variable and changed the value of it, then pass it back to the TranslationContainer Object and save it. – Rafael Paz Mar 09 '17 at 09:37
  • When I go back to my TranslationContainerTableView, same thing, nothing changed. My entities are like this: I have a TranslationContainer Object which has "to one" relationship with Dictionary Object. So, my list is retrieving all translationContainers whose property dictionary = "Portuguese" or "English" or whatever the user chooses. Am I doing much shit or Core Date is too much complicated? I was so motived with Core Data, but being stuck for a week in something that supposed to be so simple made me rethink about change to Realm or Sqlite. – Rafael Paz Mar 09 '17 at 09:37
  • if there is a one-to-one relationship between TranslationContainer and Dictionary I would recommend merging them into a single object. – Jon Rose Mar 09 '17 at 09:41
  • I did not understand you comment about passing around values and saving them. What does your core data stack look like? are you using NSPersistentContainer? – Jon Rose Mar 09 '17 at 09:42
  • that's my core data stack on appDelegate.swift: `lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "DataModel") container.loadPersistentStores(completionHandler: { storeDescription, error in if let error = error { fatalError("Could load data store: \(error)") } }) return container }()` – Rafael Paz Mar 09 '17 at 10:13
  • `lazy var managedObjectContext: NSManagedObjectContext = self.persistentContainer.viewContext` – Rafael Paz Mar 09 '17 at 10:13
  • and then, my fetchedResultsController is described on my main question – Rafael Paz Mar 09 '17 at 10:16
  • Thanks for your patient in replying me, Jon! I can't merge the 2 objects into one because I have another entity which has relationship with Dictionary as well, not just TranslationContainer. Well, I understood from your comment, that I should modify a property of TranslationContainer entity, so I just got one random object and modified on of its property and save to see if the controller would trigger and update the search, but it didn't – Rafael Paz Mar 09 '17 at 10:19
  • are you FetchedResultsController delegates setup? – Jon Rose Mar 09 '17 at 10:25
  • are you writing directly to the `viewContext` or are you using background context? – Jon Rose Mar 09 '17 at 10:26
  • Sorry again, Jon for my late reply, I have another job and sometimes I work double shifts.. Well, first of all, I would like to thank you very much for your help, it was really nice! but I solved the problem. I just replace Core Data by Realm. Realm is much, much easier to use. Realm's team did an excellent job also with their documentation. For my projects now, just Realm! Bye bye Core "Complicated" Data – Rafael Paz Mar 11 '17 at 23:48