Why would layoutIfNeeded be called upon scrolling the uitableviewcontroller? - ios

I'm trying to optimize the UITableViewController for slower devices, and after eliminating many of the reasons why a tableview might be slow, I'm still trying to figure out the last one...
This screenshot is from instruments, showing the most expensive calls, during a time where no call is made to get heightForRowAtIndexPath or cellForRowAtIndexPath (scrolling ~10px up and down repeatedly without causing any cell to go out of the table, or get rendered).
Is this normal behavior? Why would the view need relayout if the content of the page is not changing?

Every time you scroll, you change the contentOffset. contentOffset updates bounds, specifically its origin. Every bounds change marks receiver for layout need in next update cycle. In this case to update frame of floating subviews like scroll indicator, table headers and footers, ... This is totally normal behaviour and there is nothing you can do about it. Don't worry, you are redisplaying, not redrawing.

We'd have to see some code to identify exactly why that method is being called but it could be from one of these reasons at least:
You have constraints that are being changed when scrolling even if you don't see it, they might be affected, maybe inside the UITableViewCell?
It does't have anything to do with the table view, the view just has something else to layout, maybe you made a constraint modification...
Your calling a method thats calls it explicitly.
Also remember this will only layout the view 'if needed'.

Related

UICollectionView resizes cells incorrectly after reloadData

I have a UICollectionView that renders some autosizing cells. When I load the data into the collection view initially, the data is perfectly sized the way it should be.
I have a weird issue where, when I trigger reloadData with a new data set, the auto sizing goes wrong and I end up with incorrectly sized cells.
Images attached.
I have tried invalidating the collectionViewLayout before I call reloadData.
Oddly enough, if I remove data, and trigger a reloadData the cells resize to their correct layout. So I am wondering if this is a view recycling issue?
Thanks in advance.
Below is the layout after the screen was initially loaded for the first time.
Below is the layout after the + opens a modal to add a new post, closes, and refreshes the data - see incorrect sizing of cell "Thanks for all your hard work".
Below is the screen after the screen was reloaded from scratch again.
My answer might be a bit late, but getting the similar behaviour I did manage to solve it.
In my case, the problem was with multiline labels — so changing them to single-line solved the case.
As I didn't need to have multilines — I had no further research on them.
I hope, this answer could be a good starting point to find a fix.

Is there any way to call initialLayoutAttributesForAppearingItemAtIndexPath with some delay consecutively for collection view cell?

I am working on a UICollectionView that reload the cells animatedly.
I have wrote an animation code in my layout class in
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
And its works perfectly i just want to know is that somehow can i introduce an delay in cells animations so it will be like first cell reaches its place than second and then third cell and so far.
So is there any specific way to achieve this?
Unfortunately, the framework is not designed to work in that way. I would not animate the frames directly – that is a dark path that leads to pain. You'd basically need to add one model to the data source backing the collection view, wait, and repeat. It shouldn't be too hard, but it is a bit hacky.
If the appearance animation you'd like to achieve is a frame animation you can adjust the initial start position instead of adding a delay.
Let's say you want your cells to fly in from the right. For the first cell you can set the initial offset to 500px, for the second 600px for the third 700px and so on. Even though they all start animating at the same time, they'll reach their positions in succession.
Hope this helps.

Embedded scrollview in tabbar item using storyboards won't scroll

For my current project I wanted to use storyboards and autolayout instead of coding everything by hand. It has gone well so far, but my design has a section of the app where there is a tab bar and one of those tab needs to show four views. The design is to swipe between the four and so I thought to use a scroll view. After some trial and error I found that embedding a Container View in the tab allowed me to easily set up a scroll view and put a couple of view inside it, carefully aligning them using positioning to put them side by side so that each page is one child view. I'm not sure how that plays with the autolayout, and in fact I have the problem that the scroll view won't scroll past the first page position. I can drag about 1/3 of the second page into view, but it never brings that page entirely in view.
I've checked the content size and offset and all of the view positions and it all seems correct. And when I use Spark Inspector to change the scroll offset to the position of the second view/page, the app shows the right page/view and I can even scroll back to the first page. I'm a bit perplexed as to what is causing it to not scroll properly. I don't have any code to show as this is all done in storyboards, but I am wondering if anyone has any ideas as to what is wrong?
Alternately, does anyone have an idea for how to use autolayout and storyboards to set up swiping four adjacent views in a tab? I suspect there are ways to do it. I can think of ways to do it in code, but I'm trying to avoid doing it that way.
EDIT: I set the scroll view delegate to the view controller around it and checked the values of contentSize on scrollViewWillBeginDragging and scrollViewDidScroll. It is always set to {0,0} even after I force set it on viewDidLoad. So I tried setting it every time scrollViewWillBeginDragging is called, which seems work, but I don't understand why this happens and it doesn't smell right. Is there some autolayout constraint that might account for this? Does something cause contentSize get set to {0,0} during the layout process?
For lack of any other answer, I'll use my unsatisfying solution as an answer in the hopes that helps someone else in the future:
I set the scroll view delegate to the view controller around it and checked the values of contentSize on scrollViewWillBeginDragging and scrollViewDidScroll. It is always set to {0,0} even after I force set it on viewDidLoad. So I tried setting it every time scrollViewWillBeginDragging is called, which seems to work.
I'd love to know why the contentSize is being reset if anyone finds out!

Problems animating UICollectionView bounds with a nonzero contentOffset

Sigh. Second question in two days. I'm starting to not like UICollectionView quite so much.
Ok. I have a UICollectionView set up, working nicely, and a custom layout object managing the... er... layout. I'd post code but there's tons of it and I'm not certain which parts would be relevant. Mostly I just followed this tutorial: http://skeuo.com/uicollectionview-custom-layout-tutorial
Now herein lies the problem. If I scroll my collection view away from contentOffset 0,0, then cause its bounds to animate, the start location of any cells that were already visible and are remaining visible is a long way out! They arrive at the right place, but start about 60 pixels too far left and too far down. Always the same amount each time no matter how far I've scrolled from 0,0. The weird thing is, so long as I don't move away from 0,0 it all animates exactly as I want it to, no problems whatsoever. Somehow the contentOffset appears to be interfering with the current frame of my content cells, and in a completely nonsensical way.
It's really bizarre and I can't work out why it's happening. Anyone got a clue?
[EDIT] a little more information. As an experiment, I changed the layout style of my collection view so that it only changed width rather than height when it resized. Now it appears that the starting Y positions are correct, so it could well be an unusual bug involving autoresizing. For the record, I have checked my various cells and they are all set to top/left, fixed width and height, so that's not the problem.
[EDIT] And more information. It seems that, when the bounds changes, the layout view is asked if it should invalidate. Returning YES is the only way to animate the bounds change. However, what THEN happens is that the collectionView immediately sets its own bounds a second time to send the current offset back to zero/zero. The problem may be that if I do both, it gets confused, but if I only invalidate on the first call it gets the wrong information, whereas if I don't invalidate on the first, I don't get the second at all. Chicken/Egg.

Set UITableViewCell height *without* using heightForRowAtIndexPath

I'm trying to set the height of a UITableViewCell based on some dynamic content. I know you can "set" the height via the heightForRowAtIndexPath delegate method in your view controller, but... I can't do that!
The problem is that the height of the cell isn't known until cellForRowAtIndexPath is called, and heightForRowAtIndexPath is called before cellForRowAtIndexPath.
So, I need to somehow either reverse the order in which these two methods are called, or find another way to set the cell height.
Any ideas?
Sorry - there's no built-in way to reverse those methods or anything similar. One of the reasons is that a table view can want to know the height of a row for a number of reasons, not all of which include displaying a cell - -tableView:heightForRowAtIndexPath: might get called several times, or -tableView:cellForRowAtIndexPath: might not get called at all following a call to the height method.
What you can do is find another way to compute the appropriate height ahead of time, cache it, and rely on that cached value for -tableView:heightForRowAtIndexPath:. I've managed to do something similar by constructing a "dummy" UITableViewCell instance, keeping it out of the normal reuse queue, and just using it for layout and height-determination purposes. Such a solution would go something like this:
Get -tableView:heightForRowAtIndexPath:.
Look up the index path in your height cache. If you have a value, return it.
Otherwise, use your dummy cell to lay out the content that would appear at that index path. Measure the cell's height and cache it.
Potentially repeat steps 1-3 several times, depending on how much -tableView:heightForRowAtIndexPath: is called.
Get -tableView:cellForRowAtIndexPath:.
Dequeue a cell from the normal reuse queue, populate it, lay it out, and return it. (You may choose to verify that its height matches your precomputed height, so that the table view behaves how you'd expect.)
Depending on the content of your cell and the complexity of the phrase "lay it out" in your use case, this may incur a reasonable performance hit as you calculate the first several heights, or as your users scroll your table view rapidly. However, as your cache warms up, you should be computing fewer and fewer heights as your app continues to run.
One last point: remember that for cells with default heights (which may or may not appear in your table), you can early-out by returning tableView.rowHeight, rather than computing the default height every time it appears. This can ease the computation burden of the above approach somewhat.

Resources