Oddly I can't find anything on this topic anywhere.
Basically, I have a simple UITableView with, lets say, 10 rows. Each row has a user associated with it, and each user has a "profile picture". Now lets say that each row was created by 1 of 2 users, so it will be displaying 1 of 2 different profile pictures in each of the rows.
I'm trying to be memory efficient, so I don't want to load the same image 5 times in a new UIImageView. So I tried using the exact same UIImageView reference in each of the rows for the same user, but it seems that only the last row to get loaded shows the ImageView. If, for example, a row that was offscreen becomes visible and reloads, the image will then appear for that row and disappear from the row where it was previously appearing.
So my question is, how can I do this?
A. A solution to what I'm trying to do/something I'm missing
B. IOS will not load the same image into memory AGAIN if I use it in a new UIImageView, so I'm just wasting my time worrying about this
C. A new solution that blows my mind
Thanks!
Steve
*Note, I'm building this in Xamarin/Mono but I don't think C# vs Objective C would make a difference in this case
UIImage has a built-in cache, so option B is most likely your solution. Because of this cache, there is probably no need to keep a single UIImage instance around to use in each UIImageView. While that approach may speed up your application slightly, it's more likely a case of premature optimization.
In addition, UITableView is highly optimized to reuse cells that move off screen so you will only ever have as many UIImageView instances as you have visible UITableViewCells.
Your problem was caused by the fact that UIViews can only have at most one superview at a time. So every time you added your single instance to a cell, it removed itself from the previous cell. That's why you only saw it on the very last cell.
I think you are misunderstanding how iOS draws images. You are going to need to create multiple UIImageViews in every spot the image is displayed, but you can (and should) reuse your UIImage object, and that's what really consumes memory.
So in your cellForRowAtIndexPath method, simply create an imageView and set it to the image which you've already loaded.
Reuse images, not imageViews.
Related
I am experimenting with the UITableView, but animations are not occurring as I would expect.
Given a list of cells, I want to delete the top-most cell (the item at IndexPathsForVisibleRows[0]), and have all the rows beneath it, animate upwards into their new position.
However, it appears that any time IndexPathsForVisibleRows[0] (or any invisible cell ABOVE the first visible one for that matter) is touched (moved, deleted, etc), the UITableView refuses to animate the results of the transaction.
I have included 2 gifs to demonstrate the issue.
The first GIF demonstrates the desired behavior. I am removing the cell at IndexPathsForVisibleRows[1] (the second on-screen cell).
Notice how all cells below animate correctly into position.
The second GIF demonstrates what happens when removing the top-most cell (IndexPathsForVisibleRows[0]). Note how all the cells below move immediately into the new position, without animation.
(note: the entire table change is wrapped in a BeginUpdates/EndUpdates block).
Am I missing something which causes the 2 scenarios to behave differently, or I have I just stumbled upon a UITableView bug/limitation?
After much investigation, including several new code projects distilling the UITableView down to the various different factors that could be contributing to this behavior, I have found the simple answer.
Don't use the UITableView
I, like many others, have relied heavily on the UITableView in many applications, due to its simplicity and performance, however, if you really want it to behave in fluid, sensible ways, it just cannot deliver.
There are some inherent bugs in it's behavior which don't appear to bet getting addressed by Apple - and not surprisingly, when there is a much better alternative already in the SDK.
Enter the UICollectionView.
I took all my backend logic for and adapted it to supply a UICollectionView instead (all the work took about half an hour), and lo and behold, everything just worked as originally intended.
So, all my code was correct, the UITableView was just getting in the way.
This is how it now looks. Notice have all deletion scenarios animate correctly, no jankiness.
I'm not the first to have found UITableView lacking:
https://pspdfkit.com/blog/2017/the-case-for-deprecating-uitableview/
Additional benefits to UICollectionView:
customizable layouts
easily customizable cell animations
update transaction does not halt currently running scroll animations (this one is great, if you have a background thread refreshing data, you won't get a sudden 'jerk' when rows get rearranged)
As it's clear from the title, I have a scrolling performance issue in a table view.
Since I have read nearly every question that is posted online in this regard, and I assume all of you have much more experience with UITableView and its techniques, I won't bother with general stuff, and I just wanna point out some key things in my code that may help you help me spot where I'm doing wrong.
The UI in each cell is very very basic, so rendering each doesn't take considerable time. No shadows, no rounded corners, no extra effect, nothing. Just a few labels and two images, that's all.
The datasource is an NSArray which is already fetched from CoreData. The data of the labels are set from the content of the array, without much calculations or process required.
The height is each cell is a static integer, so the tableView:HeightForRowAtIndexPath: will immediately return the result as fast as possible. No calculations required.
The tableView:CellForRowAtIndexPath: dequeues and reuses cell with reusable identifiers so any re-creation is avoided.
So far everything is perfectly smooth. The issue is where items in Core Data are fetched from a server (Which is extremely fast) as user scrolls down. Data binding is done inside tableView:willDisplayCell:atIndexPath: to prevent tableView:CellForRowAtIndexPath: from becoming slow, as data needs to be loaded just before the cell goes live on the screen. I also fetch new items from server inside this method whenever there're some cells remaining till the last item fetched. So for example when there are totally 50 cells data fetched and put in the CoreData already and this method is called for cell number let's say 40, I request another 50 cell data from server, so that it will be ready whenever user reaches the end of the table.
As I expect this should only be called for the cells that go live on the screen. But putting some NSLogs shows that it is called multiple times until next 200 cells data are fetched (I guess the amount changes depending on device or simulator and the memory available on them and also OS limits). For example, I'm testing on an iPhone 7+, and I start the app and I go the page in which the table is. It fetches first 50 items and only first 4 items are shown on the screen, But I see that tableView:willDisplayCell:atIndexPath: is also called for cell #25, so another 50 is fetched immediately, and then it is called for cell #75, so another 50 is fetched, and this goes on for like first 200-300 cells, and then when fetching is stopped, scrolling is extremely fast and optimized until next 200-300 cells are fetched.
What can I do? Shouldn't tableView:willDisplayCell:atIndexPath: fire whenever a cell is about to be displayed? Where else should I fetch data as user scrolls?
Any ideas or suggestions is REALLY and GREATLY appreciated.
I am wondering what exactly happens, when you dont use dequeueReusableCellWithIdentifier in the cellForRowAtIndexPath-method.
In one Project I am collaborating we have different types of custom UITableViewCells which all appear in one single tableview. So here we fill arrays with all TableCells that should be displayed. These arrays are not very big (10-15 Cells) so for us that way works even not using any identifiers for dequeueReusableCellWithIdentifier. The next question is how at all you could use identifiers resp. dequeueReusableCellWithIdentifier when using different Cells in one single section of an UITableView. Is someone here hwo can explain, what exactly happens in background? Regards Nils
The dequeueReusableCellWithIdentifier is something that reminds me of Flyweight pattern.
Since allocation and instantiation of a cell can be an expensive task, using this mechanism you have the opportunity to create only the first visible cells and later reuse them just changing their contents. Scrolling animation must be as fast as possible to give a good experience to the user.
Is it worth it? Yes and it basically comes for free, we just need to pay attention that some old data can be still present in a new visualization, the trick is to always implement the method -prepareForReuse() correctly, here, you can eventually wipe all displayed data before setting the new one.
If you want to use different cells in the same section is absolutely possible, also if they have different height. You just need to crate different cell identifiers, one for each cells and tie them somehow along with your data.
I usually map data to be displayed in struct (swift) or dictionaries along with a key for the cell identifier to be used.
If your type of cells are representing themselves while scrolling you should dequeue them.
In my UITableViewCells I want to show a UIImageView depending on whether an event has occurred. In my custom cell I have an image assigned to the UIImageView (it's a single static local image). I then set the imageview to hidden until I want show it, and then I do imageview.hidden = NO;
My question is whether this affects the performance of the UITableView? Should I only draw the UIImageView as a subview when I need it? Thanks!
It's not only healthier to do the "imageview.hidden = NO;" thing, but it's much better performance wise.
Think of it: if you add a subview to that cell, you have to remove it before the cell gets recycled to be displayed when a different row gets displayed. It's far easier to simply do "imageview.hidden = YES" at the beginning of each of the calls to your "cellForRowAtIndex:" method than it is to go through to remove any unneeded, previously added subviews.
Since you will be recycling your cells, use UIImage imageNamed to cache the local image and then hide it as you are doing. You will get the cost of an image load once and then smooth scrolling. The biggest performance hit will be the initial fetch from disk, so best to get it out of the way before the user needs to see it
I agree with Michael for the most part, but do have one caveat.
I would use imageview.hidden = NO; That being said, it may not be best in all circumstances.
The actual performance really depends on how many imageViews you actually make visible, it is stylistically more straightforward to just make the view visible. However, if making the image visible is a relatively infrequent event then you can consider delaying loading the image until you need to add it.
For example, if only 2 out of every 50 cells actually gets a visible image, it might be better to delay loading it.
I have a uitableview that loads fairly large images in each cell and the cell heights vary depending on the size of the image. Scrolling performance is decent, but can sometimes be jerky.
I found these tips I found on the FieryRobot blog:
glassy-scrolling-with-uitableview
more-glassy-scrolling-with-uitableview
Does anyone have any tips for improving uitableview scrolling performance?
Cache the height of the rows (the table view can request this frequently)
Create a least-recently-used cache for the images used in the table (and invalidate all the inactive entries when you receive a memory warning)
Draw everything in the UITableViewCell's drawRect: if possible avoid subviews at all costs (or if you require the standard accessibility functionality, the content view's drawRect:)
Make your UITableViewCell's layer opaque (same goes for the content view if you have one)
Use the reusableCellIdentifier functionality as recommended by the UITableView examples/documentation
Avoid gradients/complicated graphical effects that aren't pre-baked into UIImages
If you are subclassing
UITableViewCell, don't use a Nib,
write it in code instead. It's much
faster than loading Nib files.
If you're using images, make sure
you're caching them so you don't
have to load from file more than
once for each (if you have the
memory -- you'd be surprised how
much space images take up).
Make as many elements opaque as
possible. Similarly, try not and use
images with transparency.
The developer behind Tweetie has written extensively about this and has some code that demonstrates how it was done for that app. Basically, he/she advocates one custom view per table cell, and drawing it manually (rather than subviewing with Interface Builder, among other options).
fast-scrolling-in-tweetie-with-uitableview
Also, Apple has updated its own sample code for TableView in its TableViewSuite tutorials (maybe in response to this?)
TableViewSuite
#1 performance killer for UITableView scrolling is drawing shadows on any cell view layer, so if scrolling performance matters then don't do shadows unless basically it doesn't slow down your main thread.
thought this had to be said since none of the accepted answers made mention of shadows and layers. :+)
Any problem with UITableView scrolling performance can be solved using techniques already described in other answers. However many a times sluggish performance is caused by something inherently erroneous, or repetitive.
The fact that UITableView reuses the cells, and the fact that each cell may need its own image - together makes the solution bit complex. From how it's being solved the general way, here I summarize things that should be taken care of:
Load data into data source - from REST / database. This step should be done on background, eventually using dispatch_async along with GCD queue.
Create and initialize relevant data model objects and putting them inside an array
[tableView reloaddata]
Inside cellForRowAtIndexPath, include code that will set data (text) from correct data model object of the array.
Now images maybe in the form of URL too, so this step might be little quirky because of cell reuse done by table view. The heart of the fact is to load once again image from device cache / URL using async queue, then set it to correct cell.image (whatever is your cell image property).
To avoid problems, refer to this tutorial about lazy loading of images inside table view.