Implement a chat app with NSFetchedResultsController - ios

All messages are ordered by message time asc (the bottom is the newest). All the messages are shown with UITableView. It will show 50 messages default, and load 20 more messages when pull down. How to implement this with CoreData NSFetchedResultsController? Does NSFetchedResultsController support pull-down paging?
I think it is NOT a good idea to do like this:
Set NSFetchRequest.fetchLimit to 50
Call performFetch method of NSFetchedResultsController
When pull down, set NSFetchRequest.fetchLimit to 70, 90, etc.
Call performFetch method of NSFetchedResultsController again
Reload the whole table view.

Before brainstorming on technical aspect of implementation it is very beneficial to carefully work out UX and overall design of the app. Here are some things that you might need to address while developing chat app:
Can message bubbles overlap?
Do you need dynamically displayed stuff like timestamps, typing animation etc.?
How would you organize partial fetch of the data so network will be used carefully?
Do you need to animate any objects in chat? (message bubbles insert, deletion and update). I mean some advanced animation that use UIDynamics.
Features listed above can be a real problem to implement with UITableView. While with UICollectionView you have all the power of custom layouts and batch updates.
For ideas listed in this post should give a credit to one of Viber Messanger developers who raised this problems on one popular russian language resource.

Instead of setting fetchLimit you could set batchSize, and let NSFetchedResultsController handle how it loads objects into memory.
Otherwise you'll have to update your fetchRequest and performFetch again.

Related

iOS UITableView: What is difference between the tableView:willDisplayCell:forRowAtIndexPath: and tableView(_:prefetchRowsAt:) methods?

Both these functions can be used for fetching paginated data as the user scrolls through the list. But are there any significant advantages of one over other? Which is better for implementing pagination?
willDisplayCell is called when the cell is about to be displayed and is subject to the developer handling corner cases in terms of repeat displays, etc. It is intended to be used to perform cheap state-update operations on the cells in question -- not to prefetch data.
prefetchRowsAt, on the other hand, is called well ahead of time and allows you 'breathing room' to kick off potentially expensive operations that your cells depend on.
The docs for prefetchRowsAt state:
The table view calls this method on the main dispatch queue as the user scrolls, providing the index paths for cells it is likely to display in the near future.
Use your implementation of this method to start any expensive data loading operations. Always load your data asynchronously and forward the results to your table's data source object. 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.
Prefetch operations can also be cancelled by the UITableView when it calls the tableView(_, cancelPrefetchingForRowsAt:) function on your data source.
So, yea, there's quite a difference. One option requires a whole lot more work than the other.
from apple documentation i think that's the difference you are looking for.
prefetchRowsAt: Providing the index paths for cells it is likely to display in the near future. Use your implementation of this method to start any expensive data loading operations.
Vs
willDisplayCell:
send message just before it uses cell to draw a row,This method gives the delegate a chance to override state-based properties set earlier by the table view, such as selection and background color

Lazy loading old messages with NSFetchedResultsController while doing realtime communication

Consider a chat application scenario, where you have a very large group with 100,000+ messages and realtime communication.
Just like most chat applications, we want the latest messages to appear at the bottom i.e. new items are added at the bottom.
What's the best practice for using lazy loading with NSFetchedResultsController? Changing the fetch-request by increasing the fetchLimit doesn't seem like a good idea. Also, using an extra array instead of fetchedObject also doesn't seem like a very elegant or convenient solution either.
What's the best practice for showing latest messages at the bottom i.e. reversing the UITableView direction? Transform doesn't seem to be an elegant solution, or is it?
Looking for an elegant solution, that's working for people. Please advise.
fetchLimit doesn't work with NSFetchedResultsController. To limit the controller you can do the following:
Do a single fetch with a fetchLimit=1 and a fetchOffset=BATCH_SIZE (where the batch size is something large, but not huge = ~200)
get the date of that message that you fetched
limit the fetchedResultsController to the date of that message.
Now you have a fetchedResultsController that is 200 messages. Note that it may increase in size as long as it is open.
When a user scroll back you can do some similar adjusting of the fetchedResultsController by doing a fetch to figure out a correct date range.
For display the cells I have used the double-inverse method (apply a 180 rotation to the collectionView and a 180 rotation to every cell). It is not that elegant but it works and it is not as expensive as it first seems - the whole screen is already in an openGL layer behinds the scenes anyways. There are a lot of little things that it caused (like the scrollIndicator being on the wrong side), but also a lot of little things that it fixed (like dealing with a chat with very few messages). If I had to do it again I would make a custom layout, but I wouldn't be so quick to dismiss the double-inverse method.

How to use NSFetchedResultsController for scrolling bottom to top in messaging app?

I am building a message app. When I am messaging in my application all the messages are getting store in core data. I have connected the core data with TableViewController with NSFetchedResultsController. I know how does this work and I am using NSFetchedResultsController for a long time with TableView. In most of the case TableView load from top and it's scroll down to load more data on requirement, thus my application works pretty well.
Here for the message app the TableView behaviour is opposite from the previous one. It loads the last messages(20-30 batch size) and if I want to see the old message I will scroll top. So I am trying to implement this on NSFetchedResultsController.
Example :
Suppose I have 100 messages. I am setting batch size is 10. So first it will load 90-100 messages and it will scroll down to the last indexpath(here 100th). Then If I want want to scroll top and reach 90th indexpath it will give me 10 again so now I have 80-100 messages now it won't scroll down it will be keep on doing the same thing is I scroll to top.
I know I can sort in reverse order but that time I will get 100-90 but it won't so the last message(100) bellow.
So how can I fix this issue? Any configuration is there for NSFetchedResultsController that I am missing? How to fix?

iOS using UITableView reloadData vs endUpdates

I am writing an app with a basic UITableView that shows NSManagedObjects that are loaded from CoreData.
There is also a background process that is running and notifies the viewController using NSNotificationCenter of any changes to the CoreData NSManagedObject (IE: Using NSInserted/Updated/DeletedObjectsKey). I have a few questions regarding the best way to update the tableView after the view receives the notification of changed data.
Is it better to call reloadData on the tableView or figure out a diff on the data and do the inserts/deletes/updates inside a tableview.BeginUpdates() - tableView endUpdates()?
Is it a valid practice to refresh the tableView while a user is interacting with the tableView?
Is there an easy way that I am missing to do a diff between two arrays of the CoreData NSManagedObjects and apply to the tableView?
I hope I am not overcomplicating things
It depends. If you want just refresh data without any animation then use reloadData. In the case when you build up an user-friendly smooth data changing animations, it would be better to calculate the diff and insert/delete rows with some beautiful animation
User is not interacting with tableView infinitely. You may track when table is not dragging and update the data, but refreshing view to show actual data is common practice today. For example, at the web it is AJAX technology. Also, as I mentioned in 1, use animation to attract user's attention to new data
As it mentioned in the comments, in your case using NSFetchedResultsController would be better

How to effectively deal with large datasets in Core Data?

I am using core data in my app to store entities that could have as many as 50k objects or more. I have this paired to an NSFetchedResultsController in a table view. The table view works fine due to cell reuse however my biggest problem is queuring the actual database to get the dataset.
When i first load the table view i need all results from the db. I am using the default fetch request with a single sort descriptor and I have set the batchSize to 1,000. On an iPad 2 this query takes up to 15 secs to finish! I also have to run this query after a search has been cancelled so overall it makes the app unusable. My assumption is that CD still has to resolve all those results or setup the sections or something, i really have no idea but just using the batchSize doesn't help?? The content is also very dynamic in the sense that new rows are always getting added, sort order changing etc.. so caching has a limited benefit.
I am thinking now that the best option would be to use a fetchLimit in the fetchRequest and then implement some basic paging. When the table view scrolls to the end fetch the next "page" of results? My only problem with this approach is that i lose the sectionIndex and i cant think of any way around that.
Anyone have any ideas or dealt with this issue already?
When you set the fetch request for the FRC the batch size should be just a few items bigger than, maybe twice the size as, the number of items that can be seen on screen at any one time. The FRC already does the pagination for you you just need to set the page size better.
s.newave,
Do your rows have variable height? If so, then the table view asks you to calculate each height and that causes every row to be fetched. 15 seconds is not an unreasonable time to fetch 50K items.
The bigger problem is your statement about not wanting to change your design. Frankly, a 50K item tableview is useless. You should change your design -- not because CD is slow, it isn't -- but because your design is not pragmatically usable.
Andrew
P.S. The fetched results controller is designed for mainstream applications. a 50K table view is not a mainstream app. If you insist on keeping with a 50K table view design, you will have to make your own controller.

Resources