UITableViewCells - ios

Can tableview cells be dynamically configured so different cells can have different heights, based on data/text content? Does this violate Human Interface Guidelines? The documentation I have found is spotty and confusing, at least to newbies like me! :)

You can specify the height for each cell by implementing the tableView:heightForRowAtIndexPath: method in your UITableViewDelegate; note this will be called for every row, even rows not currently visible, so it must be fast and you may have trouble if your table has thousands of rows.
The only statement I see regarding cells with different heights in the HIG is "Avoid variable row heights in a plain table. Variable row heights are acceptable in grouped tables, but they can make a plain table look cluttered and uneven."

The UITableViewCell does not control its own height.
To set the height of a row in the table, implement the UITableViewDelegate method tableView:heightForRowAtIndexPath:
This can be somewhat painful sometimes - since often the cell is the best place to calculate its own height. The pattern I use is to implement a class method on the custom UITableViewCell class to calculate the cell's height given the data it is displaying. Call this from your implementation of tableView:heightForRowAtIndexPath:
I've never had an app rejected for variable height rows in either plain or grouped tables.

Related

heightForRowAtIndexPath not called for every cell

I'm implementing a chat functionality in my app, and I'm rolling my own UIViewController to do so. I'm using a UITableView instead of a UICollectionView because it fits my needs better.
Some of my messages are text-based (which can be multi-line) and some of them are image-based. I also have some cells in place to show headers (timestamp information) and footers ("Sending...", "Delivered", etc.).
When I build my data source, I am calculating the height needed for each of these cells, and returning that data in my tableView:heightForRowAtIndexPath: method.
I am trying to take more control over this process because automatic sizing of cells using auto layout doesn't work all that well when I want the UITableView to spend most of its time at the bottom, rather than at the top.
So, I have the following needs:
When the view loads, it should start fully scrolled to the bottom
At that point, the UITableView's contentSize should be correctly calculated
Inserting new cells at the bottom of the UITableView should work well and also animate well.
The problem I'm finding is that only some of my cells have their height checked by heightForRowAtIndexPath:.
For instance, here's a particular case:
I have 100 messages + 32 header/footer cells (132 total cells)
In viewWillAppear: (or viewDidAppear:, it doesn't seem to matter), I call this:
int lastRow = (int)(self.dataSource.count - 1);
NSIndexPath *lastPath = [NSIndexPath indexPathForRow:lastRow inSection:0];
[self.tableView scrollToRowAtIndexPath:lastPath
atScrollPosition:UITableViewScrollPositionBottom
animated:animated];
It doesn't change much whether the animated variable is YES or NO. YES seems to call a few more times, but both of them only really call for values at the top and bottom (beginning/end) of my data source. This means that my calculated content height (which is correct) is not in line with self.tableView.contentSize.height.
If, however, I manually scroll up through all of the cells, everything gets sorted out and the UITableView is finally aligned with my calculated height.
In addition to the initial view needing to scroll to the bottom, I also want to be able to add new messages to new cells at the bottom of the UITableView and then animate them into view. That really doesn't work well if I let the UITableView manage its content size and its own animations.
Based on Apple's documentation, I expect it to call heightForRowAtIndexPath: for every cell. I'm getting that from here:
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). See also tableView:estimatedHeightForRowAtIndexPath:.
So, my basic question is how I can force UITableView to let me set all of these values. I'm using auto layout within the UITableViewCell instances themselves, but I want to have control of cell sizing, rather than letting UITableView do it. Any suggestions?
Just adding tableView.estimatedRowHeight = 0 will solve the problem.

Swift 2.1 tableView cells layout and content to change in runtime

I need a tableView which could have any number of cells with different layout and/or data in different cells and could change in run time.
With static cells, I am limited by its initial layout creation and number of cells.
With prototype. I am limited by its fixed layout even though its number and data can change.
I need to select different layout as well as data and number of cells in runtime. How do I get this behaviour?
Thanks
Use tableView:numberOfRowsInSection to dynamically change the number of cells in the UITableView depending on some model you have.
Use tableView:cellForRowAtIndexPath: to dynamically select one of your UITableViewCell subclasses, which will have a layout that you define, and populate it with data.
When your model changes, you can reload the cells in the tableView and they will change according to the logic in your UITableViewDataSource methods.

Is there a "self.tableView.estimatedRowHeight"like method for collection cells?

I'm trying to make self resizing cells for my collection view. Ill display text parts and those text parts have many sizes, since every cell will have a paragraph. Reading a guide on appcoda and useyourloaf i got the solution to make it with table view, so the only thing i can't do on the collection view cell is this
self.tableView.estimatedRowHeight
for obvious reasons I can't use it on collection view. Is there any similar thing that I can do for collectionView cells?
PS: I can't change the collection for a tableView.
PS2: I'm using swift
In theory there is; Apple has been claiming since WWDC 2014 that there are self-sizing collection view cells in exactly the same way as for self-sizing table view cells.
But in fact every time I try this feature, my app crashes. So in my view the answer is: no, if you want dynamically sized collection view cells you will have to size them dynamically yourself. This is not difficult, however. Just implement collectionView:layout:sizeForItemAtIndexPath: and set the size of the item. If you want it to be based on internal constraints, call systemLayoutSizeFittingSize: here.

Poor initial load performance with heightForRowAtIndexPath and UITableView scrollToBottom

I have a UITableView that reads information from CoreData via the proper mechanisms (using a FetchedResultsController, etc). This information is either textual, or a URL to a local image to load into the tableview.
Data needs to be populated in the table in a bottom-up fashion (similar to a messaging app). I am targeting iOS 8+, but if I use estimatedHeightForRowAtIndexPath, I get terrible jerkiness on 3+ multi line labels and images. The estimate seems way too far off unless it's a one line UILabel. My hunch is that the cell height is being estimated in a top down manner, such that cell heights are growing from top of cell to bottom of cell. This means that scrolling top to bottom is fine, but bottom to top is not, since the cell is being resized "downward" dynamically as I scroll upward.
I am currently using heightForRowAtIndexPath to calculate cell heights. The problem with this is that it takes a very long time for the view to initially load because cell heights are all calculated at once. I am using cell height caching to store cell height so that once the view has loaded, scrolling is buttery smooth.
So my question is this: how do you use heightForRowAtIndexPath without taking the 3-5 second initial load hit?
And follow up bonus question, is there any way to reliably use estimatedHeightForRowAtIndexPath when you have cells that are vastly different in height? We're talking anywhere from 44px to 300px. From what I've read, I can't use the estimatedHeight calculation at all in this situation.
I've exhausted all of the stackoverflow posts concerning estimatedHeight/heightForRowAtIndexPath and I'm now starting to look at the same posts more than once. So I'm stuck.
why woncha stuff a few rows in the table to populate the visible area and after
the viewDidAppear start stuffing older messages on top of the table one or two
at the time with animation none, automatic or whatever.
this way with the postponement of the uitableview population
me thinks you'd get a passable performance.
or you could do it the skype way, postponing population of the table
with older messages until after table bounces off the top edge.

UITableView dynamic cell heights - reload height for single row

I have a UITableView with rows of dynamic heights, many of which contain UITextViews. When the user starts typing in one of the textviews, I have the cells grow to accommodate the size of the textview content using the begin/endUpdates method. Using this method allows the cells to resize without losing keyboard focus on the textview, an important aspect of my app.
However, when I call begin/endUpdates, it reloads the heights for every cell that I have, and I was wondering if there was any way to only recompute the height of a cell at a particular indexPath while the user is typing. I want to do this because my heights for the other cells are expensive to compute as they have dynamic content. I know I could write some height caching code, but I was wondering if there was any method to only recompute the height of a specific cell or set of cells in a UITableView without losing keyboard focus / reloading the data content of that row?
I am using ios8, but will take an ios7/8 solutions.
Create of array of NSIndexPath's. If you have just one indexPath, add just this to the array, and use this method:
[self.tableView reloadRowsAtIndexPaths:[NSArray *array] withRowAnimation:UITableViewRowAnimationAutomatic];
but i think this method, will also hide the keyboard, never tried.
Doesn't seem like this is possible to do for only a single row. I ended up building a caching system for row heights so that at least it is faster to return heights for all cells.

Resources