Grouping messages similar to iPhone Message app - ios

In our iOS application, we are using core data and tied it with a table view using NSFetchedResultsController. The app is about "Chat" feature.
UI is same as that of iPhone "Messages" app. When we tap on a message, it displays the history and all the history grouped with time. The logic behind it is, if previous message and current message are received with a gap of 1 hr, then date & time stamp will be displayed over recent message.
My question is, how can I group the messages and fetch them so that I can show the date & time stamp as well as sender and receiver messages.

There are four types of message cells type - regular, group-start, group-middle, group-end. A group-start message is more than a hour after the last one but less than an hour to the next one. group-middle is less than an hour from the one before and after. group-end is close to the one before it, but more than hour to the one after it. regular is more than a hour before and after it.
There are two parts of this project. One is to display each type of cell correctly. The other is figure-out which type each message is. I assume you can figure out the UI stuff yourself (different padding, for each one, regular and group-start show the time, not rounding some corners, etc).
For each message to figure out its type, is not that hard - just look at the message before it and after it. It can be done in a single run through of the results - O(n). It could also be done lazily with a cache (ie each time a cell load check the message before and after it - save the answer in the cache for next time). If the cell sizes are different for different types then it make cause some weird jumping with estimatedRowHeight. You could also store the results of the type into core-data after you calculate it.
Be careful when a message is inserted to invalidate and recalculate the message cell type for the one above and below it. Also when calculating the message cell type account for situations where there isn't a next or previous cell.
I think you were hoping for some core-data magic - like some cleaver trick with sectionIndexKey. But it is really much more straight forward of just running through the array and calculating it.
Update:
Just to make it clear: don't use sections. Keep all the cells in one section. Just add the time to the top of the cell for the cell type group-start. It is a lot easier than dealing with sections especially when there are inserts that can cause and earlier cell to change from normal to group-start.

Related

Loading and displaying random cells in UICollectionView

Pre-requisites - Environment: iOS 9.0 or above - using Swift 3.0.1
Thanks for your responses. I'm updating the question and trying to give a better understanding about the problem.
Posting code would help may be but I'm not allowed to post the code as I do not have the IP.
But I am trying to build something like calendar/program guide where you have events for each category for several days.
Imagine, categories on your left side in a column and they can be the sections of the collectionveiw and each category has events for several days which is a row.
CAT 1 : Event 1, Event 2 ... Event n
CAT 2 : Event 1, Event 2 ... Event n
CAT 3 : Event 1, Event 2 ... Event n
.
.
.
CAT m : Event 1, Event 2 ... Event n
Problem: The entire data is pretty dynamic and humongous. I can't prefetch all the records, they are about over 80-100K. It takes few minutes to download all the data and display it on the grid.
A user could select any day and any time and I have to scroll the collection view to that day and time and display those events for the categories. Also, user could obviously scroll in both directions to and browse the events in this case the events are loaded like infinite scroll fashion.
In the former option though, when the user jumps on to a particular day and time on the entire timeline and I have to skip loading the other previous events (as I do not have them yet - unknown) and display the events relevant to the user selected days and time.
I do not have all the IndexPaths in advance, to display on the screen, how can I skip events and dynamically update the collection view in parts like we load images dynamically and the ones which get loaded first and displayed earlier than others.
I'm using startDate of the events to calculate the xPosition, categories don't change often after they are loaded so we could somehow avoid reloading sections but items in those sections change all the time and they appear in a random fashion.
When the controller loads the first set of events are fetched from the server and displayed, now if the user decided to jump to some D-Day and T-Time which could be anywhere on the entire timeline I have to fetch the events for those dates and populate the items for relevant sections (visible on screen) and update the interface. This is where I have issues, where I do not have an proper approach.
Hope this is clearer.
I have "tried" to mock this up
UICollectionViewFlowLayout can help you achieve what you want...
https://developer.apple.com/reference/uikit/uicollectionviewflowlayout
https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
You have the same problem I had with my calendar project. The solution I have implemented will not work for you, but I am mentioning it here so that it might give you clues on how to solve it for your situation.
My calendar has a function where a user can scroll to some date way into the future. The problem was that date cells can be custom sizes. Therefore, since they are scrolling to some future date, in order for me to know the destination offset, I needed to know the offsets of cells 0 -to- destinationOffset because the cell sizes are different. This meant I had to query the sizes of all the cells in the middle which led to a 2-3 lag time (or in your case, a long download time).
So here was my solution.
I originally had a delegate function called sizeForCellsAtMonth which was called for every month in order to determine the size. I have now changed this function to be called only once.
The function now only has two parameters:
defaultSizeOfCells
exceptionToDefaults - this will be specific months where the cell sizes are different
Using this information, I can calculate the sizes of all months because I know the sizes before hand. So my problem was solved by changing the way I looked at my delegate. Maybe you can try looking somewhere along those lines or maybe my answer gave you clues of what you can do.

UITableView Smooth Scrolling Issue Because of "tableView:willDisplayCell: Method"

As it's clear from the title, I have a scrolling performance issue in a table view.
Since I have read nearly every question that is posted online in this regard, and I assume all of you have much more experience with UITableView and its techniques, I won't bother with general stuff, and I just wanna point out some key things in my code that may help you help me spot where I'm doing wrong.
The UI in each cell is very very basic, so rendering each doesn't take considerable time. No shadows, no rounded corners, no extra effect, nothing. Just a few labels and two images, that's all.
The datasource is an NSArray which is already fetched from CoreData. The data of the labels are set from the content of the array, without much calculations or process required.
The height is each cell is a static integer, so the tableView:HeightForRowAtIndexPath: will immediately return the result as fast as possible. No calculations required.
The tableView:CellForRowAtIndexPath: dequeues and reuses cell with reusable identifiers so any re-creation is avoided.
So far everything is perfectly smooth. The issue is where items in Core Data are fetched from a server (Which is extremely fast) as user scrolls down. Data binding is done inside tableView:willDisplayCell:atIndexPath: to prevent tableView:CellForRowAtIndexPath: from becoming slow, as data needs to be loaded just before the cell goes live on the screen. I also fetch new items from server inside this method whenever there're some cells remaining till the last item fetched. So for example when there are totally 50 cells data fetched and put in the CoreData already and this method is called for cell number let's say 40, I request another 50 cell data from server, so that it will be ready whenever user reaches the end of the table.
As I expect this should only be called for the cells that go live on the screen. But putting some NSLogs shows that it is called multiple times until next 200 cells data are fetched (I guess the amount changes depending on device or simulator and the memory available on them and also OS limits). For example, I'm testing on an iPhone 7+, and I start the app and I go the page in which the table is. It fetches first 50 items and only first 4 items are shown on the screen, But I see that tableView:willDisplayCell:atIndexPath: is also called for cell #25, so another 50 is fetched immediately, and then it is called for cell #75, so another 50 is fetched, and this goes on for like first 200-300 cells, and then when fetching is stopped, scrolling is extremely fast and optimized until next 200-300 cells are fetched.
What can I do? Shouldn't tableView:willDisplayCell:atIndexPath: fire whenever a cell is about to be displayed? Where else should I fetch data as user scrolls?
Any ideas or suggestions is REALLY and GREATLY appreciated.

iOS: performance when there are thousands of sections in UITableView

I have thousands of sections in TableView,and I use titleForHeaderInsection to give each of them a title.But I find that when the TableView is initialized, this method will be called thousands times to give every sections a title. Is that means I should set several sections each time?
No, you don't have to.
A memory is optimised for such occasions, so if your data source is properly set and contains data regardless its size, UI should handle it safely.
UITableView in iOS app, for example, is rendering just those cells, that are displayed at current time. So for example the common tableview on the iPhone 6 is displaying about 15 rows at a time.
And as a user, when you are scrolling down (or up) the table view, each time before the certain cell is displayed, the method cellForRow is called and takes data from your data source.
This also works for sections.
But, if your data don't have to be stored in the App bundle, the best way would be to get source data asynchronously, so for example you get 30 rows from data from server, after launching the app. Then if you reach (by scrolling) 30th row, your app will request more data from server and updates the table.
So user, that won't scroll your tableView, will not download all data and will not use big data transfer.
As a conclusion I would mention, that for all situations, the smaller data source, the better for performance of your app.
Tableview works on concept of reusability so only few which are visible are created and after that rest all are reused so no need to bother about it .
Rest refer to this link : Apple official link : https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html
Go through life cycle of Uitableview datasource methods as each and every method will get called for each section and row. So it is completely fine if section method is called thousand times. If you have any issue for it then add a paging like load first 10 sections and then other 10 sections and go on.

WatchKit Table flickering when updated

I am currently working on a watchkit app and ran into a problem with a flickering table.
The situation is as follows: With the storyboard tool, I created a table containing two row types.
The concept is, that when the data for the table is being downloaded, there is only one row of the first type which will use the whole space to indicate, that data is being downloaded. When the data arrives the second row type is used to display the data.
The problem is, that the table is somehow flickering, while it is being updated with the data. I was able to fix this problem by removing the download indication message und using only the one row type for data.
My question is, if someone did run into a similar problem or if there is any better way/pattern to display this kind of information messages, which show the user whats going on when he is using the app.
The WatchKit Table will flicker when reloading when a visible cell is different size than the other cells. Make sure they are all the same height and width and you will not have a flicker.
The way I did it was to put a group above my table that took up the entire screen with a loading animation inside it. I then hide/show the groups as needed.

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