How to make a table view which can be scrolled for ever? - iphone

I have a set of 100 rows, pretty similar to values which can be selected in a picker. When the user scrolls the table, I want the rows to be appended like an forever-ongoing assembly-belt. So when the user scrolls down and reaches the row 100, and scrolls even further, the table view will show again row 1, and so on. Reverse direction same thing.
My thoughts:
don't display scroll indicators (they would make not much sense, probably)
what value to return in the numberOfRows delegate method? This infinity constant?
in cellForRowAtIndexPath: simply wrap the index around when it exceeds bounds?

Many apps do this. The idea is that the beginning of the table is always the same, but you keep adding to the end, so the table just keeps growing.
Let's say you first have 100 data elements. Your numberOfRows returns 101 then. First 100 cells are normal. And scroll indicators still make sense.
If the 101st cell is displayed, you display a progress indicator like UIActivityIndicator in the cell, and initiate the process to load next 100 rows. When the data arrives, you either reload the whole table with UITableView reloadData, or you insert new cells individually with UITableView insertRowsAtIndexPaths:withRowAnimation:.
So, you just keep infinitely appending to the table. This is easier than trying to keep the table as a "window" that is always at N cells/rows and unload from the beginning of the table. If you need this sort of window, you may reconsider if this is really the best way to interact with your data.

I just finished implementing a variation of this.
Lets assume you have a 100 rows and 8 of them fit in a screen. Start by adding the last 8 rows first, then the 100 rows normally and then the first 8 rows at the end. Recenter the tableview by using setContentOffset to the first row of the 100.
When the user scrolls below the 100th one he will see rows 1-8, you can detect this in the viewDidScroll delegate function and recenter the scroll back to the first record (ensure u set the animated flag to NO when you do this). Similarly when the user scrolls past 1, he/she will see the last 8 records, you can again recenter the scroll view to show the last 8 records in the 100 row section.
This actually implements circular scrolling and the user can keep scrolling in both directions.

I haven't tried this but here's an idea. Let's say N = 100, since you have 100 rows. What we'll do is tell the tableview we have 200 rows, but we'll keep the user scrolling in the range 50-150.
Tell (the tableview) that you have double that number of rows, so for numberOfRows, return 2*N. In cellForRowAtIndexPath, always return the cell corresponding to (row % N).
Now init the table scrolled to row N (instead of 0) so that the user can scroll in either direction.
As the user scrolls up the tableview will eventually ask for row N+(N/2), when that happens send a scrollToRowAtIndexPath:atScrollPositionanimated: to scroll to the row-N. That will reposition the table back by N (ie: from 150 to 50), same cell but we'll never run off the end of the table this way.
Do the same when the tableview asks for cell at row N-(N/2) for scrolling up.
You'll have to deal with the special case where N cells fits in the view with extra room, ie: what if N were 1?

Related

AutoLayout of Custom Accordion/Collapsible UITableViewCell

I would like to build a TableView where when the user clicks on a cell it expands to show more information. My question is how do I use Autolayout to arrange the multiple items in each cell.
Each cell will always be the same size, whether it is collapsed or not, so the sizing isn't dynamic.
The first problem I have is how to use Autolayout to arrange all the items in the cell. Before Xcode 7 I was successfully using Autolayout where I would pick a label-button-view to arrange, click Editor > Align > Trailing/Leading/Top Space, to.. etc. This is now greyed out and I don't know how to replace my old strategy.
Each cell has two rows of items. The first row shows all the time, the second only shows on collapse. Below is a picture of how the cell will look when it is collapsed:
The first row is a bit trickier because outlet1 and outlet 2 will have variable sizes. I would like 'label' to come right after label1, no matter how long or short that outlet happens to be. As arranged currently, there is a variable amount of space between the two.
What I'm looking to achieve in row 1 is basically exactly like Venmo:
Notice how 'paid' conforms to the size of the two names in the first row.
The second row has two buttons and an outlet which will always be the same size.
To sum up - how can I layout these elements in the UITableView for iphones4 thru 6S - and then how do I make this cell a collapsible cell? The construction of these cells seem to work as a system, not isolated from the whole - which is why this is a 2-part question.
is this what you want to achieve?

Why does calling insertRowsAtIndexPaths: call heightForRow for all rows?

heightForRowAtIndexPath is a very expensive operation when you have a lot of cells. In my case, I have thousands, so I thought I might break the table down in batches and display only 50 cells a time, and as the user nears the end of the table view bounds, use insertRowsAtIndexPaths: to insert 50 more rows. However, everytime I insert 50 new rows, heightForRowAtIndexPath is called for all rows of the table, not just the ones I just inserted. Is this expected behavior? Why is this? My height for row has to calculate the height of a string with a font, so for it to calculate heights for the entire table every time I insert new rows is extremely ineffiecient. Are there any workarounds?
Is this expected behavior? Why is this?
Yes, this makes it possible to insert rows with different heights into one table.
Are there any workarounds?
Calculate the height only once during initialization in case all rows have equal heights.
Every time heightForRowAtIndexPath is called, simply return the cached result.
I agree with #Anne. It needs to be called because a row's position in the table (which is just a scroll view) is established by the position and height of the rows above it.
And #Anne's prescription is pretty much right, too, which boils down to making the height method run as fast as it can.
But the real question is for you. I think the "performance" question you should be asking is about human performance, thumbing through 1000's of rows.
Let's say one flick of the thumb can scroll 10 rows and be performed in a second. That's 1.5 minutes per thousand. A million users using your app to scroll 5000 rows once per day would consume 1.9 million workdays each year, reducing national GDP proportionally.
I wish you the utmost success, but please consider the precarious state of the global economy before distributing an app with 1000s of UITableView rows.

Show a label over the limits of a UITableViewCell

I'd like to use a UItableView to show a day Calendar. Each row corresponds to one hour. I need to show the hour between 2 cell of my tableview.
Like this :
(source: free.fr)
And this is my UITableViewCell :
(source: free.fr)
In the first screenshot, it works perfectly but if I scroll down then scroll up, my time label is cut like this :
(source: free.fr)
Have you any tips to figure out this problem using a tableView ?
The way you lay out your cell now is fragile, because the order of painting the cells on screen matters a lot. Try moving the content up so that your buttons are flush with the top of the cell, and the time label fits into the cell entirely. Add a thin header view to your table to make the top cell appear normal. Keeping the content of a cell entirely within its bounds should help you maintain reasonable scrolling speeds.
EDIT : You could also put a second clipped label at the top of your cell, and make its content identical to that of the label in the prior row. You would need to take special care to hide that label in the top row, but otherwise this should make your table immune to changes in the rendering order of its cells.
Make the background color of your cell clear. As you scroll up the z ordering of your cells get reversed and the lower cells overlap the higher ones causing this clipping.

heightForRowAtIndexPath being called for all rows & how many rows in a UITableView before performance issues?

I thought I had read that for a UITableView that heightForRowAtIndexPath doesn't get called on all rows, but only on the ones that will be visible. This isn't what I'm seeing however. I'm seeing hundreds of calls to heightForRowAtIndexPath for the simple situation of the orientation being changed of the iPhone for example.
So I'm assuming here therefore that for a UITableView with heightForRowAtIndexPath implemented, it does (i.e. heightForRowAtIndexPath) get called for all rows (not just the visible ones)...let me know if this isn't quite correct.
QUESTION: Given the above, how many rows in a UITableView (where heightForRowAtIndexPath is implemented) can you have before performance issues occur typically?
Is there a way around the performance issues? i.e. set a nominal/standard height for each row and not implement heightForRowAtIndexPath, but then correctly set each row height only when it is displayed and set it correctly here...but which method would one do this in?
Have a look at the discussion section in the tableView:heightForRowAtIndexPath: documentation
The method allows the delegate to specify rows with varying heights. If this method is implemented, the value it returns overrides the value specified for the rowHeight property of UITableView for the given row.
There are performance implications to using tableView:heightForRowAtIndexPath: instead of the rowHeight property. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).
So you should use the rowHeight property of the UITableView. If you need different heights you are out of luck because you have to use tableView:heightForRowAtIndexPath:.
AFAIK there is no way to change the row height at display.
The tableview has to know the correct size before, otherewise there would be ugly position shifts all the time.
I think I found a solution to that.
In iOS 7 apple introduced some new tableview properties. One of them is the:
tableView:estimatedHeightForRowAtIndexPath:
So if you supply an estimated row height, for example, then when tableView:heightForRowAtIndexPath: is called repeatedly before the table is displayed, it is called only for the visible cells of the table; for the remaining cells, the estimated height is used.
Here is the source for that information: https://books.google.gr/books?id=wLaVBQAAQBAJ&pg=PT521&lpg=PT521&dq=heightforrowatindexpath+only+for+the+visible+cells&source=bl&ots=7tuwaMT5zV&sig=h3q8AaFvoCgcrPu2fQchVkIEjwg&hl=en&sa=X&ved=0CEMQ6AEwBWoVChMInLK0xbPuxwIVCbUaCh3_nQWG#v=onepage&q=heightforrowatindexpath%20only%20for%20the%20visible%20cells&f=false
My goodness, I spent over an hour trying to find the source of my performance problem!
Finally I also found hundreds of calls to heightForRowAtIndexPath and a search
got me this thread. THAT is really annoying.
Performance goes down here already when just displaying 250 items. Thankfully the cells I want to display now all have the same size. But I could imagine someone wanting to display some different cells for a tableView with > 200 items!
FIX THIS APPLE!
Cheers
A way to improve performance in tableViews with a big number of rows and dynamic cell heights is to cache the height of the cells once they are first calculated.
A simplistic approach to achieve this is to keep a NSMutableDictionary in which the key is the id of the record in the cell (or any other identifier you might have), and the value is a NSNumber with the height of the row. Once the height is first calculated, store it the NSMutableDictionary by the record id. In the tableView:heightForRowAtIndexPath and tableView:estimatedHeightForRowAtIndexPath: you check for a cached height in the dictionary and return it if found. If not found, calculate the height, and store in the cache before returning the height.
You might have to be careful with invalidating the cache for the rows that change heights. For example, if you have an expand button in one of your cells, you will need to remove the height of that cell from cache once the expand button is tapped delegate method for height is called.
You might still have a performance hit if you try to display 1000 of cells at once when the table shows, as it will likely call the height method for each row. A work around for that is to first warm the cache, if possible in a background task, before first displaying the cells.

Select one table cell and have multiple cells momentarily highlight

I have related items in adjacent table cells. When either of the related items is selected, before going to the detail view, I would like the momentary cell selection highlight to show both (or in some cases 3) cells highlighted to alert the user of the relationshp.
This a purely a "nice to have" cosmetic feature.
Any help appreciated.
In the method called when the user selects a row you can use:
[cell setSelected:YES];
One way to get the cell would be using tableView:cellForRowAtIndexPath method (correct me if I'm wrong).
* FOLLOW-UP TO Shaggy Frog *
I appreciate your comments.
For some reason, I can't add a comment to or edit my own original question so I am editing my follow-up.
May I have an opinion on the following. Since the 2 highlightd cells would always be adjacent and the data related, it makes sense that the detail view contain details for both cells. However your point is valid and I don't want to get rejected.
Instead of 2 cells, if for the "related cells" I were to double the height of those cells, draw a horizontal line half way between (to make the 2 "subcells" of 44 pixels in height looking just like 2 cells) and place the data in subviews so the result looks exactly the same as 2 cells but it would actually be 1 cell. Selection of one of my "subcells" would then highlight 2 "subcells" which is the entire cell - same effect but 1 cell.
Do you think that would pass?
And of course the biggest advantage is that I know how to do this versus the multi-cell highlighting which doesn't seem possible.