Description
I have a CoreData entry called Person, I fetch it using NSFetchedResultsController, with a fetchRequest ordered by property "name". Then I display the "name" in the table view cell.
Problem
When I change the entity's "name" property and the rows reorder, NSFetchedResultsControllerDelegate does give me a NSFetchedResultsChangeType.move. But the "name" displayed on the cell is outdated, meaning I'm not receiving NSFetchedResultsChangeType.update
The Table View Programming Guide: Inserting and Deleting Rows and Sections says batch updates do updates first, then deletions, lastly insertions.
It defers any insertions of rows or sections until after it has handled the deletions of rows or sections. The table view behaves the same way with reloading methods called inside an update blockāthe reload takes place with respect to the indexes of rows and sections before the animation block is executed. This behavior happens regardless of the ordering of the insertion, deletion, and reloading method calls.
Question
Any idea on how the notifications sent by NSFetchedResultsController are implemented, specifically on the ordering of insert, delete, update & move?
Or how can I use some kind of code to efficiently (meaning partial update, not reload all data) solve this particular problem?
Your setup is non-standard. The sectionNameKeyPath is really meant for sections not rows. Fetch the Person entity and populate the cell with a person's name directly via itemForRowAtIndexPath.
You will then have the expected change types available.
Related
I have a collectionView with NSFetchedResultsController. Some cells in the collectionView will have extra embedded UI elements queried from core data, and those extra embedded UI elements also need the update functionality of NSFetchedResultsController.
So my question is, what is the recommended way of approaching this?
Since the number of cells, and whether or not each of them has embedded UI elements depends on the data actually fetched from server, we cannot use sectionKeyPath of NSFRC right?
EDIT: the extra UI elements are not the same model as the embedding cells and thus require separate queries (NSPredicate).
EDIT: Our core data model:
RelationModel
type
status
Relationships(fromProfile, toProfile)
ProfileModel
..many fields
Relationships(photos)
Basically, the extra UI elements will be toProfiles with the embedding cell being the fromProfile. But because there are more than one kind of relations in the app, we decided to have a separate model for relations. And I found it hard to set a relationship from ProfileModel to the RelationModel
A NSFetchedResultsController is a really cool object. It does a fetch and then monitors core-data for changes. While it has an interface that relies on indexPaths so it is natural to think of these indexPath's as the same indexPaths as your collectionView there is no requirement that you do that. The indexPaths of the fetchedResultsController can be different than the indexPaths of the collectionView - you just need to careful about keeping track which indexPaths you are dealing with and translating from one to the other.
For example: You have a set of widgets that you fetching from core data. Some of the widgets have a property of extraWidgetInfo which you want to display in you UI as an extra cell. The fetchedResultsController says that there are 4 element (all in section 0). But the collectionView can display that as
[section1] widget1,
[section2] widget2, widget 2 extra info,
[section3] widget3,
[section4] widget4, widget 4 extra info.
While the fetchedResultsController only says that there are 4 elements, there are 6 cells in the collectionView. You would also have to translate the fetchedResultsController indexPath when dealing with updates. An update would translate to a reload section, and an add would translate to an insertSection and insert of some amount of rows in that section. You could also just call reloadData when core data updates (If you data is updating rarely this may not be a bad option).
Since we require separate queries in each cell, we ended up setting separate NSFetchedResultsController in each cell when it's dequeued. And then have the NSFetchedResultsController set as nil when prepareForReuse.
UITableView updates between beginUpdates and endUpdates calls are batched together and all performed at the same time. Apple's documentation is specific about the order in which insert and delete operations are performed:
Deletion and reloading operations within an animation block specify which rows and sections in the original table should be removed or reloaded; insertions specify which rows and sections should be added to the resulting table. The index paths used to identify sections and rows follow this model. Inserting or removing an item in a mutable array, on the other hand, may affect the array index used for the successive insertion or removal operation; for example, if you insert an item at a certain index, the indexes of all subsequent items in the array are incremented.
It's also worth noting that:
Calls to beginUpdates and endUpdates can be nested; all indexes are treated as if there were only the outer update block.
[Emphasis mine]
So: think about deletions as occurring in a first pass, and then any insertions happening following this, using the new index paths that result after the deletions.
That's useful. However, I've not found any documentation about where row (and section) moves occur, which indexing they should use, and whether this impacts on the other steps. Anyone know?
When moving table row, you specify two indexes:
indexPath in original table from where to take this row
newIndexPath in resulting table where this row will appear
func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath)
Possible conflicting operations in single batch, causing app crash:
trying to move a row you also deleting
trying to move same row to multiple destinations
trying to move several rows to the same destination
trying to move a row to the same destination where you insert new row
It is not possible to move a row you are inserting, because it was not there in original table.
By default, moved row will be not reloaded from data source.
I've got a UITableView that needs to display two sets of search results, with each set of search results being a different entity.
Each entity needs to display its search results in a specific section. I need to use an NSFetchResultsController so that if additional data becomes available the tableview automatically updates.
My question is, how do I associate a specific NSFetchedResultsController with a specific section? By this I mean I want all of the fetch results for entity 1 to be in section 0, while all of the search fetch results from entity 2 to be in section 1 of the tableview.
I've no problem having a tableview with a single section / single fetch controller, but is there anyway to have an NSFetchedResultsController be associated with a specific section of the table view?
After you set up the two NSFetchedResultsControllers, in the number of sections delegate method, specify 2 sections, for the delegate method for number of rows in a section, look at the section number and get the count from the appropriate fetched results controller, and in the cellForRowAtIndexPath: method, get the data from the appropriate fetched results controller based on the section number in the index path.
I am using a FetchedResultsController to fetch the data for my UITableView. The data is created via actions performed on another tab (so my table may have 5 items but if I switched tabs and go back to my table, it may have more than 5 items that it should display). My table can potentially contain many rows. Right now I am using [myFetchedResultsController performFetch] in my viewDidLoad.It appears that when I create data in my other tab, when I switch back to my table tab, that new data is put into my table automatically without me perfomring [myFetchedResultsController performFetch] again. Here are my questions:
1) Does a fetchedResultsController automatically monitor the manajedObjectContext for changes and fetches the new objects if they come into existence? (This appears to be what is happening but I just want to make sure. Perhaps I have some code that is helping me do this that I forgot I put in somewhere)
2) Does the fetch performed by [myFetchedResultsController performFetch] fetch all of the objects at that time, or does it fetch only what it can fit in the view of the table and it fetches the rest later as it needs it (as you scroll in the table)? I ask because since my table can potentially have a lot of rows, it seems inefficient to fetch all the data at once if only ~12 of them will be displayed on the table at once.
EDIT: I just realized that in my FetchedResultsController delegate methods, I have
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView reloadData];
}
Am I correct in saying that a fetchedResultsController monitors for change, but will not apply it to the table unless the table is reloaded as I have done? If so, then I have another question about UITableView. Does reloading the table only reload the rows in view and then the other rows are updated as you scroll? Again I ask because if my data is very large, it seems inefficient to reload the entire table if it will reload all rows at once.
Yes, if you add a delegate
You should set the fetch request batch size when you configure the FRC because it can only load an appropriate number of items for the screen if you tell it how many that is.
You apply the changes, the FRC just collects and supplies the data. The delegate method tells you about a change. Reloading affects the whole table in terms of row count but only shows the visible rows (assuming the batch size is set appropriately).
I've a UITableViewDataSource which maintains sections of data items which will be presented by an UITableView instance. The table view is editable, allowing insertion and deletion of rows and sections, and all changes on the view should be written back to the data source. After reading through the Apple documents, I can deal with insertion and deletion on rows by sending message tableView:commitEditingStyle:forRowAtIndexPath: to the data source.
But, however, I can't figure out what is the standard way to feedback the changes on sections to the data source. Please kindly help.
The delegate method tableView:commitEditingStyle:forRowAtIndexPath: is called by the table view to tell you what the user has done (what they have added / removed). It is then the responsibility of your code to make the appropriate changes to your Model and reload the table view.
The simplest way to reload is to call reloadData, and you also have more specific options for reloading / inserting / removing individual rows or sections - this is all from a UI perspective and the table view requires that you have updated your Model before you call any of these methods or the table view will throw an exception.