UITableViewDataSourcePrefetching->prefetchRowsAt always has 0 and 1 in indexPaths array - ios

I have used UITableViewDiffableDataSource as the datasource for my tableView.
On initial load, 8 items are fetched from server and displayed.
When I try to scroll,
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
is called, but indexPaths always has 0 and/or 1. It is never getting incremented to ask for prefetching 10, 11,12, etc rows.
What am I missing here?

From Apple Docs:
Table views do not call this method for cells they require immediately, so your data source object must also be able to fetch the data itself.
This approach is offered by the docs:
The tableView(:prefetchRowsAt:) method is not necessarily called for every cell in the table view. Your implementation of tableView(:cellForRowAt:) must therefore be able to cope with the following potential situations:
Data has been loaded via the prefetch request, and is ready to be displayed.
Data is currently being prefetched, but is not yet available.
Data has not yet been requested.
One approach that handles all of these situations is to use Operation to load the data for each row. You create the Operation object and store it in the prefetch method. The data source method can then either retrieve the operation and the result, or create it if it doesn’t exist.

Related

No changed section info althought that change causes controllerDidChangeContent call tableView.reloadData() and in turn invoke numberOfSections

This is very weird situtaion of using NSFetchedResultsController with core data in iOS development by Swift.
At first, FetchedResultsController contains only info of non-changed sections but no changed section info, althought that change causes controllerDidChangeContent method call tableView.reloadData method and in turn invoke numberOfSections method.
But subsequent manual invokes, e.g. in viewWillAppear, of the FetchedResultsController results in correct showing of the changed section info along with other non-changed section info.
In other words, fetched change of an attribute referred in sectionNameKeyPath via cloudkit in a background context was saved to viewContext which in turn saved it to persistent container. During the process, controllerDidChangeContent was invoked and tableView.reloadData was called. At that moment, in numberOfSections, no changed section info can be observed in the fetchedResultsController.sections, which only contains info of non-changed sections. I have set that attribute name as first sort descriptor in the fetch request associated with the fetchedResultsController.
I can't really find any clue of the illogical; any thought is welcome.
Issue found!!! Credit giving to Michael in answering the NSFetchedResultsController calls didChangeObject delete instead of update
Cause is my predicate using string interpolation for number value. After changing the use of number predicate, magic happens. Thanks Stackoverflow community!

Reload UiTableView After Data Has Been Retrieved From Database - Swift

How do I reload a UiTableView only after the data has been loaded from the database. I have a datasource class that contains the function to load in all the data, and stores the data after it has been loaded into an array.
When I access the data using the Tableview though it displays nothing because the tableview loads before the database has returned its data. The only way to load the data is for me to have a button that manually reloads the tableview.
How do I cause the tableview to automatically reload once the array in the datasource object has been populated from the database?
Being a new programmer it took me a while to learn this was even possible, but I just realized completion blocks are the way to go. It will carry out some specified code only after your function has finished fetching from the database. In this case, I can update the tableview AFTER I've retrieved the data from the database. Here's an example.
Function definition:
func getDataFromDatabase(someInput: Any, completion: #escaping(String?) -> Void) {
let retrievedData: String = databaseCallFunction()
// 'Get data from database' code goes here
// In this example, the data received will be of type 'String?'
// Once you've got your data from the database, which in this case is a string
// Call your completion block as follows
completion(retrievedData)
}
How to call the function:
getDataFromDatabase(someInput: "Example", completion: { (data) in
// Do something with the 'data' which in this example is a string from the database
// Then the tableview can be updated
let tableView.title = data
tableView.reloadTableView
})
The code in the completion block will happen AFTER the database call has completed.
Why not just call tableView.reloadData() once you've populated your data? It'll repopulate the entire table (well technically it'll only re-create whatever cells are visible).
The point is that your tableView:cellForRowAt: method should have the ability to access your data so as to fill in each table cell appropriately based on that data. Anytime your data changes, just call tableView's reloadData() again. It's efficient in that only the visible cells will be created, and the rest will be created whenever they are about to become visible, if ever.
Also, tableView:numberOfRowsInSection: should similarly be looking at your data to judge how many rows shall exist. It too will be called again during each call to reloadData().
Your data should always be prepared to be interrogated by the tableView delegate methods, even when it doesn't contain anything yet. When there's no data, your tableView:numberOfRowsInSection: would decide there should be 0 rows.

Displaying Leaderboard using UITableViewController with Firebase backend (Swift 4.1)

I am using Firebase to store my backend data. I am trying to display a leaderboard using a UITableViewController in Swift.
One of the data source methods include,
override func tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
which in my case requires me to fetch data from Firebase and store the data in a local array variable e.g var leaderboardArr = [Int]()
Problem arises when there is a change to the rankings in the leaderboard. I know Firebase has built-in methods to sort the data and adjust the rank accordingly but how do I manage the local array variable?
UITableViewController dataSource method displays the data according to the indices of the local array variable.
How to manipulate the array according to the changes in data from Firebase??
When the user refreshes (or when you decide the view should refresh) retrieve the leaderboard stats from Firebase again and then reload the table view.
If you're looking to update the data in real time you can add a ChildEventListener. So when the array changes, it activates the listener, and from there you can retrieve the necessary data and reload the view.
Here is some reference: https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener

Implement UITableViewDataSourcePrefetching with an NSFetchedResultsController data source

I have a UITableView that gets data from an NSFetchedResultsController. To this end, i basically copied Apples example implementation from https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/nsfetchedresultscontroller.html.
Now at WWDC16 Apple announced the UITableViewDataSourcePrefetching protocol, which provides callbacks that let you prefetch data so it's already loaded when it's needed to be displayed by the tableview. I'm looking for an example on how to integrate this with the NSFetchedResultsController, because i can't figure out if i'm doing this correctly.
Should i simply create a dictionary, as an in-memory cache, to hold prefetched data and be used in cellForRowAtIndexPath instead of querying fetchedResultsController.object(at: indexPath) directly?
As i understand it, CoreData already caches fetched data automatically, so maybe i just need to call fetchedResultsController.object(at: indexPath) in the prefetch callbacks, to ensure the data gets cached?
Or shouldn't i prefetch data at all when using a fetched results controller, because i would work around the magical integration provided by Apple?
Or something else entirely?
*edit*: I found a slide from the Core Data talk at WWDC16 that supposedly explains this, but i understand it at all.
An async fetch request? I thought they don't work with NSFetchedResultsController. I guess that's why it's performed on the managedObjectContext directly?
The async fetch request is created from the results of calling .performFetch() on the NSFetchedResultsController. Nothings showing up until i call that. But since all the results are there after calling it, i don't get why i would need to prefetch them again.
It will improve performance if your model has faults. Whether your models have faults or not depends on iOS optimization, and how you create your schema and relationships.
So to be safe your could add the prefetch code. I have the code for Swift 4.
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
let fetchRequest: NSFetchRequest<MyModel> = MyModel.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
let items = indexPaths.map { fetchedResultsController.object(at: $0) }
fetchRequest.predicate = NSPredicate(format: "SELF IN %#", items)
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest)
do {
try fetchedResultsController.managedObjectContext.execute(asyncFetchRequest)
} catch { }
}
What it does is pretty self explanatory. It runs an async fetch request on the managed object context, therefore resolving faults, if any.
In short, you are creating a custom predicate to "warm" the predicted specific objects in the MOC. The idea, I think, is that the table view is telling you more targeted scroll target info (based on velocity etc.) about where it thinks the table view will end up. This allows you to execute an async request to pre-fetch these objects in the MOC so that by the time you are actually asking for properties on it, they're ready to go.
This is why you also have a nil completion on the async request. You aren't directly using the results because you may not actually be displaying any of these index paths yet. It also means it's unnecessary to do additional tracking (like in a Dictionary, etc.) of results when you get pre-fetched objects.
The real question is, if you are using an FRC with batching, pre-fetched properties, etc., is this really buying you anything to side-step it? I'm not really sure. I haven't noticed a huge difference between a pretty highly tuned FRC+UITableView with vs without pre-fetching on a table of around 1000 results. It's also possible by merely touching those objects that the FRC will see that (because you are sharing the same MOC) and then will run through it's property/relationship pre-fetching, etc. I haven't seen much documentation on this mechanism, but if Apple is suggesting it, especially using an FRC, I have to think it helps.

NSFetchedResultsController Delegate Methods not called on initial fetch

Question thats been bugging me here:
Are NSFetchedResultsController "controllerDidChangeContent" etc delegate methods supposed to be called when initially fetching content or only when that initially fetched content is updated / changed?
Having an issue where even though the initial fetch comes back with the results, the delegate methods are not called unless that initial results batch changes (from a network request later on for example).
This means i currently need to force a collection view update with a reloadData() since waiting for the delegate methods to call fails when there is only existing content in core data and nothing new changing it.
I have confirmed that at the time the initial fetch completes, the delegate is set and the results are valid. Any gotchas i'm missing here?
The delegate methods are only called for changes made after performFetch: has been called. You can infer this from several statements in the class reference documentation.

Resources