Wrong scroll position with estimatedHeightForRowAtIndexPath - ios

I'm building a chat using UITableView. Naturally, the row heights of each chat message cell will vary widely. I've correctly calculated the row height for each cell and the Table View performs as it should.
However, I want to implement estimatedHeightForRowAtIndexPath: to speed up performance in case of many messages. The problem with this is that it affects the scrolling behaviour of scrollToRowAtIndexPath:atScrollPosition:Animated:.
In my viewWillAppear life cycle method I tell the Table View to scroll down to the latest message (i.e. the bottom message), which performs well if I don't use estimatedHeightForRowAtIndexPath:, but as soon as I do it seems as if only the value returned from this method is used to calculate the scroll position and I end up in the wrong position.
Is it possible to benefit from the efficiency gains of using estimatedHeightForRowAtIndexPath: without it affecting scroll behaviour?

UITableView is a subclass of UIScrollView so the method
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
of UIScrollView may help you to achieve what you want

Related

Dynamic cell height issue with UITableViewCell autolayout jerk while scrolling

I am trying to do something like loading up different type of cells with custom height in a uitableview. The tableview cells are subclassed and consists of labels with the respective constraints. Each cell is having a dynamic height.
Now even before my table reloads the data, I am calculating the height that is required for the resizing of the cells and caching it in my model class so that I dont have to calculate the height when the data is rendered on the device.
To calculate height i did use the tutorial from Ray Wenderlich and I am having the right set of heights applies to the objects.
Now the problem comes. Whenever I am dequeueing the cells there is a
kind of a small jerk that gives me an indication that my cell is
dequeued while scrolling.
How can i make these movement smooth so that there is no jerk while scrolling the view ?
The height is getting assigned in and does get the value as per the current type of data getting loaded.
estimatedRowForIndexPath
Also I am calling layoutIfNeeded from my cellForAtindexPath
Suggestions are most welcome.
It's very hard to say without seeing your code in cellForRowAtIndexPath, and without seeing your cells and their respective code. Here are some general questions I would investigate:
What is the content of the cells and how complex is the view hierarchy in the cell?
Even though you are supplying the correct estimated height, an autolayout pass still needs to happen, and a complex view hierarchy will take time to resolve
Does the cell contain images?
Images that need to be decompressed from a file (UIImage imageNamed:) can be intensive and cause scrolling issues, check images are not bigger than they need to be. If needed, bump this work onto a background thread.
Are you calling a complex method to configure the cell for display in cellForRowAtIndexPath?
Look at the work actually being done in cellForRowAtIndexPath, is there a complex method being triggered in you cell subclass or view model?
Are you adding and removing views to the cell view hierarchy in cellForRowAtIndexPath?
If views are being added, removed, created, inflated from a xib, constrained etc during the cell config, this could slow things down. Try to do only what is strictly needed. Check if there is any code being run internally in the cell subclass during cellForRowAtIndexPath that could be moved to cells initWith... or awakeFromNib methods (ie code that could just run once when the cell is created, rather than every time the cell is displayed)
Also run the Instruments time profiler, see if that offers any more clues

Keeping reuse capabilities of UITableView when it is nested in a UIScrollView

To implement a rather intricate design of a screen in an iOS app, I have a UITableView nested inside of a UIScrollView.
To keep the logic simple, I implemented a method on the UITableView that calculates its entire height, and i use the result of that method and set a constraint on the nested table view, so that the scrolling logic can be solely on the UIScrollView to deal with. (I forward methods such as scrollRectToVisible from the UITableView to the UIScrollView)
While this works great with small data sets, I have recently discovered the the reuse capabilities of the UITableView are not used, because the framework believes the entire UITableView to be visible when I set that height constraint. A simple log method in the cellForRowAtIndexPath method shows all cells get calculated at once.
My question is, is there anything I can do where I would be able to tell the nested UITableView how much of it is actually visible on screen, and to only compute those visible cells?
I basically need to override whatever part of UITableView that is responsible for calculating what cells should be visible on screen.
The table view will think of itself as filling its whole frame with cells. If you limit the height it will limit the cell count visible. Are you using the deque with reuse identifier method (if not see below)
How can I recycle UITableViewCell objects created from a XIB?

Adding a Scrolling Subview to a UITableView (NOT a Cell)

I'm creating my views programmatically. I have a UITableView in my UIViewController subclass that I want to add a scrolling subview to that is not a cell. I want to add some text-based subview to the UITableView that scrolls with the table and starts out above y=0 so the user will only see it if he pushes the table down. That is, it should reside above the first section of my table. If it helps for visualization, I intend to make something similar to those "scroll down to refresh" features and want some indication to the user that scrolling down causes a refresh. Is there any way to do this without something messy like using another UITableViewCell to represent it or abusing the UITableView delegate methods to move a view around whenever the user scrolls?
Simply using [tableView addSubview:] in my viewWillLoad only makes it appear for a split-second then disappear once the table data is loaded. This seems weird to me because UITableView is a subclass of UIScrollView, which is meant to hold other views in it. Using [tableView.backgroundView addSubview] does nothing.
P.S. Why not use a UIRefreshControl for this? I'm still undecided but leaning towards not using one because I don't like how slow that spinning wheel "feels" when the refreshes are usually very very quick. I've been looking at other options like flashing the background subtly and only showing a wheel if it's taking a longer time than usual.
I You can implement pull to refresh with only a table view
To do this using the scroll view delegate, since tableview is a subclass of scroll view.
Set view controller to be the tableview delegate and implement
(void)scrollViewDidScroll:(UIScrollView *)scrollView
When scrollview content offset y value passed a point, add a label to the viewcontroller.view not tableview. When you scroll back or release, remove the view.
You might also be able to add label to the table view and set the frame origin to negative y value, so when you pull the label will move into view (Never tested this do might not work)

UITableViewCell Reuse issues with horizontal tableview

I have a slightly complicated system that I am having some reuse issues with, wanted to get some feedback. Basically it is a vertical tableview, and each cell contains another tableview that is rotated 90 degrees, so that each cell scrolls horizontally. Each horizontal cell is also set up to scroll infinitely with paginated responses from an API. I am having issues where cells are copying on top of each other when the vertical table is scrolled down. I have reuse identifiers set up correctly and in each of the horizontal tableviews I am running the following:
- (void)prepareForReuse
{
[_horizontalTableView reloadData];
}
If I turn off reusing cells the issue doesn't happen, but the vertical scrolling performance suffers. I am wondering if its possible that reusing cells in this type of a set up just isnt possible? Any experience with this is helpful. Thanks.
This is not a direct way to solve your issue, but I believe if you use a horizontal scroll view inside each vertical cell, you will have this done faster and with less weird behaviours. I also believe this is not a standard way, so weird stuff will happen.
All you do is set or extend the content size of the scroll off screen continuously to create an infinite scrolling behaviour. Create views within the scroll view pragmatically to simulate each cell. Hope this helps.

With multiple UITableViews in a single UIScrollview, how to only load cell data for visible cells

I have 3 uitableviews in a single uiscrollview. Each uitableview is full length and not scrollable so that the outer uiscrollview scrolls them together. This works fine except the uitableviews believe all cells are visible so that all are created up front. Even this is acceptable except each call has an image view (a thumbnail) that is loaded asynchronously from a url. I am trying to figure out how to limit the image loading to only visible cells but still allow the user to scroll the outer uiscrollview (thus mimicking the uitableview behavior).
The alternative design of a single table with cells that show 3 cells each doesn't work (based on other design requirements) so I am stuck with some way to limit the image downloads. The largest number of cells will be 125 or so. The uiscrollview delegate doesn't seem to have enough calls to allow updating cells on the fly but I could be wrong. Any ideas?
Maybe just do a custom check : if the tableView is not visible (because not in the bounds of your scrollView), then do not load the images (or the cells) of the tableView in "cellForRowAtIndexPath".
If the tableView is visible, then call reloadData and display the images.
You can check all this with the scrollViewDelegate methods.
TableViewDatasource Protocol implements:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
for a full table that is not in bounds you can limit update simply by comparing the tableview pointer parameter to your actively showing tableviews and skip your non-visible tableviews.
For Cells: This is a more difficult one considering that you are implementing 3 on the same scrollview. This would be a bit easier if you implemented 3 separate tableviews each that are standardly implemented. The reason for this statements is, that the routine if implemented above by Apple's protocol really does only get called for the cells that are currently needing to be on screen. In this way you could implement your image background loader inside of the above defined routine, and you would indeed get what you wish. I have done this and it does work.
Another answer:
perhaps you should look into a custom tableview where you define your own custom look and feel for a single tableview that incorporates all the information you wish into this single table thus allowing you to implement the other half stated just above.
To give a better answer, I think I would have to dig deeper into what you are attempting to ultimately accomplish.
Appears I can simply use the UIScrollViewDelegate scrollViewDidScroll call and then use the scrollview's contentOffset.y to trigger the thumbnail for any cells which are visible (or about to be) using tableView indexPathsForRowsInRect for each tableview. My cell subclass has a method to trigger the thumbnail download.
The scrollViewDidScroll delegate method seems to be called for every pixel as you scroll which is perfect. I thought it might be too slow but so far it's not a big deal. The only issue to make sure I always check the visible cells if I sort them or something.

Resources