I have a UITableView with Custom UITableViewCell's. The Cell's can vary between 200.0 and 300.0 and height.
Now if you open Facebook, you'll notice the scrolling in the News Feed is awfully choppy. However, this is likely due to that various videos that are loaded on every cell, and that the time taken in cellForRowAtIndexPath is greater than 13.67ms (1s/60frames). Hence the choppy 'flicker'.
Quora on the other hand has cells that are varying heights, between probably 150.0 and 400.0+. It scrolls pretty smoothly however.
Now, when I scroll my tableView it's as though my screen is 'jumping'. I will scroll down, and every now and then (may every new cell dequeue but not quite), it's as if something higher up in the tableview got a few pixels shorter in height, and therefore the screen i'm currently viewing jumped up a few pixels. (This is not what's happening, I'm just trying to paint a picture of what it looks like).
Now I know it's not a time related issue. My cellForRowAtIndexMethod is nowhere near 13.67ms. It's always around 3ms, so that's not the issue.
2 questions...
1) Is this typically indicative of an auto layout issue?
2) Is there an accurate/semi-accurate way to calculate estiamtedRowHeight when you have such varied cell heights, or would having an accurate number here not fix this issue anyway? Just an idea.
Related
Nowadays fortunately it's trivial to have an iOS table where every cell has a dynamic height. So in the cell vertical constraints ..
---- top of content view
- vertical constraint to
-- UILabel, with, .lines set to zero
- vertical constraint to
---- bottom of content view
Assume the UILabel texts vary greatly one word, 20 words, 100 words,
In the table set
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200 // say
and you're done, these days it works perfectly of course.
However, I had the common situation where you load the table, imagine ten cells.
I populate the UILabel with "Loading..."
Only then - say, a second or two later - do we get the information for the text content. It arrives say a second later and the cell changes the text to "Some long text .. with many lines".
In fact I was surprised to learn it seems UITableView does NOT handle this. The cell in question gets stuck on the original short height.
So, after the larger text is set, I tried all permutations of the usual:
maintext.sizeToFit()
contentView.layoutSubviews()
contentView.layoutIfNeeded()
on the cell, doesn't work.
I tried sending a setNeedsLayout and/or layoutIfNeeded to the table itself, doesn't work.
I thought about .reloadData() on the table itself but - doh - that would again trigger the content being drawn from the server and loaded again, so that's not ideal.
Please note that:
Obviously there are any number of workarounds for the specific example such as not using dynamic data
I am completely aware how to manually animate the height of one cell (like when you "expand" one to show something else when the user taps)
This question is about autolayout and table view - which, thanks Apple, nowadays flawlessly handles completely dynamic cell heights involving UILabels with lines zero.
But what about if the text in such a label changes?
It seems that the table view system does NOT handle this.
Surely there's a way?
When the content of a cell changes the layout (in this case, the height) you must inform the table view that the layout has changed.
This is commonly done with either:
tableView.beginUpdates()
tableView.endUpdates()
or:
tableView.performBatchUpdates(_:completion:)
Why is that not triggered automatically?
I suppose it could be to allow you to do your own animation, or you may want to delay the update, or some other reason that doesn't come to mind at the moment.
Or, it may be due to maintaining backward compatibility?
I don't know. I imagine Apple could tell us...
I have a UICollectionView for which cell heights can vary, while the width is full-width (I'm not using a UITableView because I'll need a different layout for iPad in the near future).
Straight to the point first: the weird thing is that dequeueing doesn't really ... dequeue. Cells never really get reused: if there are 40 items, 40 cells are getting initialised, and only then are they reused. I added a print("initialising cell") call in the cell's init method, and it prints as many times as there are cells.
I'm currently using auto-sizing cells, but I've also used the "keep a cell dummy property around to calculate the size" approach, and the outcome was the same. I even pre-calculated all the heights, before showing any data, basically not spending any time in preferredLayoutAttributesFitting or sizeForItemAt.
With self-sizing:
I have set the estimatedSize on the flow layout to CGSize(width: view.width, height: 1) - if I use a "real" average height, the contentOffset jumps around when scrolling;
I'm overriding preferredLayoutAttributesFitting, and here I'm calling let newAttributes = super.preferredLayoutAttributesFitting(layoutAttributes), then, before returning them, I'm setting newAttributes.width = layoutAttributes.width, since that's the width I actually need;
I have also tried not calling super, but layoutIfNeeded and systemLayoutSizeFitting, but the results were the same, even slightly worse
I also tried this, but by returning the cached sizes if they were already calculated, but the results were the same;
I also tried this, but by calling super as well, but the results were the same;
Without self-sizing:
Calculate the heights in sizeForItemAt with my cell dummy;
Pre-calculate all the heights before displaying any data, and returning those in sizeForItemAt.
Replacing the text view with a label:
if I don't use preferredMaxLayoutWidth, it has the same problem;
if I do set it (no matter if in init or layoutSubviews), the problem is there at first, but goes away when reusing starts.
The text for the UITextView is 99% of the times under 5-6 rows, and I'm using it for the link detector.
I checked Time Profiler thoroughly, and there are spikes, but not where I'd expect them. It appears that on each spike, the most time is spent in cellForItemAt.
This is an average sample of the spikes:
UIView(CALayerDelegate) layoutSublayersOfLayer has a whopping 76% Weight;
layoutSubviews has 37%;
cellForItemAt has 26%;
The init of the cell has 18.5%;
The init of my UITextView subclass has a whopping 9.2%, half of the whole cell's init;
My update cell method has 7.4%;
_updateConstraintsAsNecessaryAndApplyLayoutFromEngine has 27.7%;
There's a UIView(internal) _didMoveFromWindowToWindow at 10%;
Some image assignment at 16%, but removing any image assigning code doesn't reduce the stutter whatsoever.
All the relevant code can be found here: https://gist.github.com/rolandleth/ab5453b39a2cfe5c83119cb79ac3dc09
If any other info is missing/is required, please let me know.
Edit: The CreateCell (which has just an imageView and a textView) behaves exactly the same, so the number of subviews/constraints isn't a culprit here - I'm just eliminating any possibility I find.
My tableView's cellForRowAtIndexPath is taking ~20-25ms at times, although typically closer to ~15, and this is causing issues.
35% of that is coming from dequeuing the cell.
60% of it is coming from calling [cell layoutIfNeeded] (which is the only solution that has worked for me with the bug/glitch in iOS8 for Self Sizing Table View Cells [more here: http://useyourloaf.com/blog/2014/08/07/self-sizing-table-view-cells.html]).
Is this a ton of time in terms of screen flicker?
When I scroll the table view, it can often get a tad jumpy. I'm wondering if this has to do with the amount of time the data is taking to lay itself out, or if this is more likely an auto layout issue I need to correct.
Any ideas out there if 20-25ms is an inordinate amount of time, or is that well under the radar?
I'm using a nav bar orchestrated set of tableviewcontrollers to display a hierarchy of data. The bottom level of data is displayed in a custom tableviewcell, which has its content described in a xib, and results in a required cell height larger than the default. I posted another question on how to programmatically extract the resulting cell height, but no usable answers, so I now implement heightForRowAtIndexpath to return a hardcoded value that is the height in the xib attributes panel. But, when the table is displayed with more rows than can fit in the normal display size (480h,320w), the bottom row is chopped off as expected, but I can't scroll it to see the rest of the row. I've searched rather extensively, but nothing has helped. I poked at various attributes (tableview sizes, scrollingEnabled, etc.) but they all seem as expected values (e.g., scrollingEnabled is true). One post suggested that no scrolling would occur unless the contentSize was larger then the frame, so I looked at those values, and it sounds promising, in that my frame size is a typical 460h x 320w, but, my contentSize is 0 x 0! Further searching (e.g., "setting contentSize" or "contentSize is 0") didn't clear anything up. I thought contentSize was computed for free from the table row count and their heights, so how could I be getting 0? Even going back to allowing a default row height by not implementing heightForRowAtIndexpath still resulted in a 0 contentSize. Some searches suggest turning off autoLayout for the custom table cell, but still the same. So, what might be going awry? Thanks for any thoughts or guidance.
Update: Sorry, I confess! I had originally been developing on a mac mini with a regular USB mouse, where scrolling in the simulator was left button down and drag. I recently switched to an iMac with the magic mouse. I'm now so used to just dragging my finger on the mouse to scroll code, web pages, etc., that I didn't think to try clicking and holding the left mouse "button", and then dragging the mouse. When I did, everything worked as expected. I had really thought things were hopeless when any similar variables/values in the app on the mini were equal to those on the iMac, but it was just me getting tricked by fancy technology. Sorry for the distraction.
set your tableView's frame and your cell's frame correct,and implement
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath in your UITableView to give a right height. If that doesn't work try set your tableView.autoresizingMask to UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
which can help you with the size of the tableview
I had a similar problem. My tableView would't scroll when the content exceeded the tableViews frame.
The problem for me was that I had "Use Autolayout" checked but no constrains on the tableView. Adding vertical and horizontal space constrains on the tableView fixed my problem
Can someone please give me a hint on how to recreate the scrolling effect used in the UltraVisual iPhone app? Here's a gif to illustrate the effect:
The first "cell" is full height while the other displayed cells are regular sized. While the user scrolls up, the first cell slowly animates to the regular height, while the next one slowly gets bigger. Do they use an UITableView? Or an UIScrollView? I have no idea how it's made...
Ha, you made my day! I actually wrote that view :)
This is actually very straightforward. This view uses UICollectionView with a custom UICollectionViewLayout.
The general principle is this. I make up a 'drag interval' – that is the required distance to drag between each cell. This value is arbitrary but affects how much the user has to drag to switch cells. The total height of the collection view is the 'drag interval' * the number of items in the view. Then I set the layout to automatically paginate to the nearest drag interval (which gives it the snapping behavior). This is very similar to how coverflow works. From this you can calculate the index of the 'top cell' by dividing the contentOffset.y by the height.
With the 'top cell' index you can generate the frames for each cell pretty easily. The top cell's frame is { 0, contentOffset.y, 320, 176 }, and from there you can calculate the next cells frame and so forth.
Then the last trick is calculating the interpolation of the page index. This is basically the decimal part of the current cell index. This will give a number between 0 and 1 that can be used to calculate the interpolation between the top frame and the frame below.
Every 'prepareLayout' calculates the frames of the cells on screen, and then in layoutAttributesForElementsInRect:, generate all the layoutAttributes based on the generated frames.
Using this trick you can create all sorts of complicated layouts. UICollectionView can be a powerful beast, but definitely takes a bit to wrap your head around it.
It's very cool! We made a fairly simple to use control like this that can be found here:
https://github.com/RobotsAndPencils/RPSlidingMenu