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
Related
So, my problem is pretty simple. I have a UITableView in a UIViewController. The tableview has dynamic cells (custom cells with images , text, etc.. from a subclass I created) and these cells belong to a subclass I created. Everything is going fine since the content is different on each cell. The problem is that when I scroll the scrollview of the cell which is horizontal ( I have a UIScrollView in the subview of the cell. I created this scrollview on the cell subclass), on let's say, indexPath.row == 0, and scroll the tableview vertically, after about 8 cells, the ninth cell has the scrollview scrolled as well. This is because of dequeuereusablecells, so the ninth cell is actually the first one, only displaying different content but since it is the first cell, it has the same background operation (the scrollview scrolled).
I tried to unscroll in the cellForRowAtIndexPath: but although this solves the problem it creates another : The first cell that was scrolled is not anymore. So, to solve this new problem I am planning on adding a workaround here that is a dictionary of [Int:Bool], that is, the indexPath corresponding to a Boolean value. If I scrolled the first cell, then 0:true. If I reach the ninth cell (which is equal to the first cell, but with IndexPath = 8 ), I unscroll the cell's horizontal scrollview. If I come back to the beginning of the tableview and reach the first cell, I scroll the cell's horizontal scrollview back. What do you guys think?
The other workaround I can think of is just not use dequeue reusable cells since I don't think I will have more than 30-50 rows on my tableview.
In terms of performance, which operation is better?
Firstly, I would suggest you to carefully analyse whether removing scrolling offset from the first cell is such a bad idea. If I am a user and I scroll down to the very bottom, making the first cell invisible, are you sure that I want it to return to the scrolled position after I scroll back to top? The answer might be Yes, but you should think about this carefully because it might lead you to the simplest solution.
However, in case you really need to do it, I would have a variable which stores the state of the scrolling for each row. If you have an array of objects from which you pull necessary properties (title, image etc) you can add this as an extra property to these objects. Otherwise, you can create an array which will be storing this info (I would prefer an array over dictionary for this task).
Fundamentally it's an issue caused by table view cell reusability. You can have workarounds, but I'd say that, things that aren't supposed to be reused should not be reused.
If your cells are all similar (with minor differences), you could use just one cell identifier; If you have very different types of cells, say, one type has a horizontal scroll view inside, one type just has some labels and images, you might want to consider to have two identifiers, so cells that have UIScrollView won't be reused for normal cells.
And at the same time, you can still do necessary cleanup works in prepareForReuse: to make sure cells that just get dequeued have a fresh start.
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?
I have created my own layout by subclassing the UICollectionViewLayout class, and not the UICollectionViewFlowLayout one. Hence it seems that I cannot implement the method collectionView:layout:sizeForItemAtIndexPath:.
Unfortunately, I may want to change my cell's sizes dynamically. For instance, I have a set a little circular cells around a big cell. When the user exchange a small cell with the big one, I want the little cell to take the location and the size of the big cell, while the big cell would get as small as the little cell and it would take its location. I already managed to do the location exchange (with the moveItemAtIndexPath:toIndexPath: method) but the cell won't change their size.
I understand that the layout is the only responsible for updating item's size, but I can assure that I have tried anything and the cells still don't resize.
Does anyone have an idea of what I could do? I would be very grateful!
Custom UITableViewCell's subviews added in code using auto layout works (verified). However the whole point of doing this was to not have to calculate the height of each tableview cell and the delegate method heightForRowAtIndexPath expects a height while drawing the tableview.
How can I figure out this height based on content using the auto-layout (visual format language based addition in code already added and working) and return it to this heightForRowAtIndexPath?
Also I'm I can't really do this on a background thread (or can I?) and therefore if I have a UITableView using this backed by a list of say 1000 records, this will take forever, is that right?
Autolayout in this case just means that you don't need to calculate the frame sizes of your subviews within each cell. It's got nothing to do with the heightForRowAtIndexPath method - this is used by the table view to define the cell's frame, which will then inform the layout of the subviews.
Using Autolayout to determine the heights would likely be pretty slow, and you can't do it on a background thread. If you have 1000 rows then I'd consider a hierarchical structure instead of a single table as that will be pretty tedious to scroll through. You could also consider calculating the heights at the point of setting or creating the data.
You may be able to set up an offscreen view using your constraints, populate it with your data for each item, then record and cache the height. However you'd have to do this at the data end rather than in the height method, as it would be far too slow.
Context:
Building an app that populates a table that takes in data from a asyc json dump.
The cells are of a custom class (I defined). The main label in the cell can be very long.
It is "placed" in storyboard within a prototype cell but customized via code (pretty standard stuff).
Labels are resized in cellForRowAtIndexPath and rows are resized via heightForRowAtIndexPath -- rows are resized by forcing a call to cellForRowAtIndex like Massimo's answer here
So per the question at hand - I've noticed some interesting (bad) things that happen.
First issue: When the table loads, the rows and labels are dynamically resized correctly! Great! However, when I scroll down and then scroll back up, the label heights will be incorrect -- (for example) the first row was correct at loading. Then when I scroll down and then scroll back up to see it again, it will be truncated. Specifically, the row size will be fine but the label height will change and become truncated to 2 lines only. Wondering if this is because I did both storyboard and coding to customize the cell. Anybody see this before?
Second issue: When I scroll down, while the rows are sized correctly (large), the labels are short (truncated.) Wondering if it's some reverse of the above "potential answer".
"potential answer" is that the rows are all calculated and stored "up front" so that scrolling down/then back up doesn't affect it. However, when cells go "out of view" and are dequeued then when they re-viewed (scroll down/then back up) it will rely on the storyboard.(inappropriately?)
All three of your issues are symptomatic of returning the wrong height in heightForRowAtIndexPath. In my data model classes I have a calculateHeight method that I call in heightForRowAtIndexPath. The model also caches the answer so it doesn't have to recalculate it after the first call. The cell class uses the model's calculated height to layout its subviews.
"ANSWERED" by deleting the prototype cell from the storyboard and making them fully in code, the issue went away. The fundamental workings are still not understood (ie. the interactions between storyboard vs. code when cells are put queued and then viewed again)