I've started a small tech demo project just to get a feel for core Data. I want a table view loaded up that keeps up to date with the data stored using core data.
I think I've implemented the data model and saving the notes correctly in this. However it's not updating on the table. I'm not sure what I'm doing wrong, perhaps I haven't linked something up correctly or something?
I've checked the persistent store on my file system and the note I created is in there so I know the core data is writing to file OK.
Any help would be appreciated. I've uploaded the project as a zip to avoid pasting the whole class here. The delegate and data source of the table is set to the view controller in my storyboard too.
The project is at: https://dl.dropboxusercontent.com/u/12457690/cdtest.zip
I am afraid there are a several problems:
You have not implemented any of the NSFetchedResultsControllerDelegate methods.
How do you expect the table view to update automatically?
In your setManagedObjectContext: method, you create a fetched results controller, but don't set the delegate.
Your performFetch method is never called. Even if it were called, it would not call
performFetch: on the fetched results controller because no predicate has been set (why this restriction?).
In your deleteNote: method, you should just delete the managed object. Updating the
table view accordingly is done by the fetched results controller (if implemented correctly).
Perhaps start by implementing the NSFetchedResultsControllerDelegate methods (there is
sample code in the documentation of that protocol). Then set breakpoints in your code
and check if the delegate of the fetched results controller is set, and check if
the initial performFetch: is called.
Related
I have a UITableViewController with a model property declared in it. When migrating from UITableViewDataSource protocol conformance to a subclass of UITableViewDiffableDataSource outside of the table view controller, the model is no longer accessible in the data source.
What would be a recommended approach to sharing a data model between the two, so say a cell deletion delegate callback in the diffable data source can reflect the change in the model property and CloudKit database.
I believe it's really up to architecture of your choice.
But it general assuming you have ViewModel(in MVVM) or Presenter(in MVP/VIPER) you should keep your model there, and then bind it to DataSource.
When deletion delegate will be hit, you need to call a delete func on your VM/Presenter, which will update this model, save it to CloudKit, and then trigger DataSource update
If you want to have a more code-oriented answer, please share an example of your code, and i'll be glad to help you out with it.
I'm working on caching for an app using a subclass of NSCache. It's working well, though I'm having a hard time wrapping my mind around how to update items in the cache when necessary and propagate those changes throughout the app. Say there is a model class, Article. An instance of Article is cached, and a few view controllers in the app are observing relevant properties on this instance with KVO, so when any changes are made to the properties of this Article, the changes automatically propagate.
But say another request for that Article is made, and the cached version is now stale. A request is made to the network and a new, updated instance is serialized, so the cache and the view controllers displaying the old instance need to be updated. How can I propagate this change to the view controllers? Is there a way to observe not just a property, but observe the reference itself to get notified when it changes? In other words, do something like oldArticle = newArticle and have an observer on the oldArticle fire?
I have a few ideas of how to handle it, but none I particularly like. I could individually transfer the property values of the new Article to the old one so the relevant observers are fired - yuck. I could use an object proxy, and set up an observer on the proxy when the object that it references changes. I could use NotificationCenter or set up some delegates or something to notify the view controllers of the update. But is there a simpler way to do something as described above?
One approach to observing the whole article is to make the cache a mutable dictionary (maybe yours is already) where the keys are something essential to the article (like an id) and the values are articles. Observing view controllers can then observe both the cache's dictionary (where the key path is article id) -- to observe whole-article updates -- and any specific values they wish on the article itself.
Sticking with KVO, another approach is to give article and updateWith: (or copyFrom:) method that takes another article parameter, updating itself with the props from that parameter. The VCs in this case will keep watching the same object, and their existing update logic will work.
I would create an ArticleUpdateDelegate, and make your ArticleViewController conform. The VC then sets itself the delegate of your Article, so that whenever the article is updated, you notify the VC, which can then update its view, and do whatever else it needs.
I have an issue where if I make changes to a core data object, save then refreshing the object, causes my NSFetchedResultsController to show a duplicate object. I think I understand what's going on, but I'm looking for someone to confirm, and also to hopefully give some more detail as to why.
To explain in more dtail I have two entities, Fixture and Position. A Fixture has many Positions, and a Position belongs to only one Fixture. To reproduce the issue I do the following:
Fetch all Positions.
Modify some value (any one) on that objects Fixture. I.E foo.fixture.name = "foobar"
Save the context
Refresh objects by calling context.refreshAllObjects, or context.refreshObject(foo, mergeChanges: false/true).
I have a tableview using a fetched results controller which displays Fixures. After doing the above the tableview will display duplicates for each item (it doesn't matter if I use the delegate methods of the FRC to do the update or I just reload the tableview).
It appears what's happening is that the refresh invalidates the objects that the FRC knows about, while at the same time gets knowledge of another set of objects. If, as step #5, I call frc.performFetch() then the problem goes away.
Other things to note:
No matter how many times I run the code I only get two of each object (I'm using a random button to trigger it for testing).
init(entityName, context) is called on my Fixture subclass as soon as I access the Fixture property of my object during the next code run (i.e after refresh was called).
In my sample everything is taking place on the same context (though it happens with child contexts as well)
To give some more context as to how I got myself in this situation in the first place users can click on a fixture in the list and then ultimately narrow down on a single position a few screens later where they can perform actions that modify the fixture. There are other active areas of the application at this point that are listening to the NSManagedObjectContextDidSaveNotification and I want them to update their objects so they can display the correct data, which is why I was calling refresh.
I've dug around in the docs and I can't see anything that specifically explains my theory that updating the context causes NSFetchedResultsController to have invalid objects. Can anyone shed some light on this behavior?
First, you really should not be overriding -init... on a NSManagedObject. That is one of the methods that you are strongly discouraged from overriding and can very easily be the source of your issues.
Second, your entire UI should be using a single instance of the NSManagedObjectContext that is associated to the main queue and therefore there should only be ONE instance of any particular entity in your UI. If you have multiple contexts you are just making things more complicated for yourself. If you are using a single context keep in mind that no matter how many times you fetch an object against that context you will get the exact same pointer back.
The NSFetchedResultsController will never create objects, it only fetches and provides them for display. Therefore the NSFetchedResultsController is only reporting the fact that you have created this duplication somewhere else in your code.
Now some questions, do these duplicates get pushed down to disk?
Can you see them in the store file on disk?
When you re-launch your application are the duplicates still there?
When you put a print in your custom -init methods on the NSManagedObject; do they fire more than once?
I have a separate object for the data source of a UITableViewController. I want to modify the data source dynamically but I don't want to have a direct reference of the tableView object inside the data source. How can I notify the controller about the changes in the data source?
Currently I use the notification center but I don't believe this is the right choice.
It would be appropriate to give the data source a reference to the table view to be refreshed or to add a property which is a block that the data source calls when some data has been updated (this block is supplied by the view controller and just reloads the table view).
The block approach is a little more generic and allows your data source to more easily be used with table / collection views.
In both cases, the relationship you're looking at is direct and 1 to 1. This is not a suitable place to use notifications. Notifications are for non-direct, broadcast type 1 to many communication requirements.
It is ok to send an NSNotification telling the UITableView that the model has changed, read the data again and reload the table.
I got struct at the thing, One alternative I fount was KVO but I did not get much success implementing KVO, So I am also thinking to use Block. And you preferred which approach ?
I am using a simple Master Detail scenario where the master uses a fetched results controller to populate its tableview. When a row is selected, the respective NSManagedObject is passed to the detail view controller where it is used to populate a few UILabels. I added an EditorViewController to allow some of the fields to be updated by the user. On save, I dismiss the EditorViewController and go back to the Detail view controller. The save uses the context assigned to the NSManagedObject.
What is the best way to know that the object in question changed so I can update the UI in the Detail VC? Currently, if I try to update the UILabels in the Detail VC, I don't see any of the new values for the object's properties. I know a delegate/protocol scenario would likely work but I am curious if there something already in place that I can use – a notification I can look for to update the object or if I should have a fetched results controller on the details screen? Not sure the proper path to take here.
NSFetchedResultsController is a most common solution to this problem. It listens for changes in the context and updates the table view's content accordingly. Your question is rather general, so I'll leave a link to a very good article showing how to use it properly.
You can use NSFetchedResultsController and register fetch request for that object (I think it is good solution). Alternatively you can listen for notifications like NSManagedObjectContextDidSaveNotification, but I would not recommend it.
Or you can pass an object to your EditorViewController and update properties there directly.
It's hard to say without seeing any code what you are going for, but the other common pattern for working with CoreData makes use of the Model-View-Controller(MVC) paradigm to stage info from your data store in a class variable, then your interface consumes from that class variable. You worry about saving, updating and refreshing the data between CoreData and your class variable separately from your interface consuming that data.
A typical workflow would then be:
//User does something that creates new info
//Store that info permanently in CoreData
//Refresh the class Variable by loading from CoreData (you can also skip this by updating the class variable simultaneously while saving to CoreData, but this is really sloppy and problematic. Better to save then re-fetch)
//Update your interface from the class Variable
Alternately, you could try Key Value Observing? but I'm not sure it's right for your situation.