I have somewhat of an odd problem. I've already got a UITableView setup with custom heights for my table cells. The problem comes in when the content is loaded in the background for the cells. I need the cells to load asap, but theres a big image for each cell, that may take some time to load (from the network or flash), so the load is started in the background when the cell is first loaded. This means that the height for the cell is initially wrong, and needs to be updated later with a minimum of glitching. The problem I'm having is that it doesn't always want to update the table, or the image, and can cause some excess scrolling.
It's also incredibly slow. I have some optimizations I need to do to make it a bit less slow (make smaller copies of the main image, and cache the height for the cells temporarily).
I've tried all sorts of things. I make sure the table is told to update in the main thread, I've tried a bare set of beginUpdates/endUpdates, reloadRows/reloadSections (table is in grouped mode, with one row per section). I have yet to get the table to update properly once the image has finished loading.
If anyone has some insight into how to get this to work as smoothly as possible I'd appreciate it.
The place to go is your heightForRowAtIndex: method. It needs to know if the image exists or not and return a value accordingly. Every time each row finishes loading it's image, you should call reloadRows~ method like this:
NSArray *indexPaths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:x inSection:y]];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
I'm not entirely sure if this will cause some heavy scrolling on your tableView, but it's an option.
Another option is to update in chunks. Say every 5-10 images (depending on how many you have) that get loaded reload their respective rows with that same method.
Related
I have a custom section header in my UITableView that contains A UICollectionView of some avatar images. The data is sourced once and cached for the images (images change so infrequently that it doesn't warrant real time updates).
I was hoping to make it so this header never redraws again even if the UITableView refresh is called. The reasoning for this is every time you take action, it causes the images to flicker as they're being redrawn from their default anonymous silhouette to the actual image of the person. The images are cached, but it doesn't matter because
I assume this is not possible by design - a UITableView will destroy everything and reload it all over again every time the refresh is called, correct?
I just wish I could hook into the refresh and preserve the section header, and reload the rows only.
Thanks for any ideas/guidance, I know this is a little uncommon but I don't want to move the header out into its own view because i'm using a UITableViewController directly, and it would be a real pain to have to embed it in a containerview and all that.
Yes, even you cached the image and set by local or something, it will take time to load. If you are using lazy loading, it will be splash ( for sure :) ). So, the only possible thing is, you have to update your tableView after refreshing is using tableView.beginUpdates and tableView.endUpdates. Input some add cell or remove cell base on the data after refreshing
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.
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.
I'm reloading a collection view, and cell reuse order seems to be non-deterministic.
It's not a problem in itself, but I'm loading images asynchronously with SDWebImage and filtering them with GPUImage (which also works asynchronously, as far as I can tell). The result is nasty flickering, even when the collection view content does not change, because it would almost always swap adjacent cells.
Of course, I can add some synchronous in-memory cache that would have post-processed images, so the image is set before the cell is configured, as opposed to a callback later, but it comes at the expense of needless increase in complexity, which I want to avoid.
Is there a way to reload individual collection view cells "in place" without collection view cell reuse kicking in?
Instead of full reload, batch updates should be used to insert and delete individual rows. It keeps existing cells in place.
Before
[collectionView reloadData];
After
[collectionView performBatchUpdates:^() {
// Use insertItemsAtIndexPaths, deleteItemsAtIndexPaths,
// insertSections, deleteSections
// to add and delete sections and rows
} completion:nil];
The downside is that refresh logic can get much more complicated, as you need to diff previous and current data sources.
All,
I hope most of you know that with ios7 there is not need to do a null check for tableview reuse
if (cell == nil) {
But unfortunately, because of that the cells are always reinitialized, as we put the code in the same method for initializing values. The problem is only with text fields inside the tableview though.
Let me explain the scenario. I have a table view with multiple rows, and some rows contain multiple text boxes. I populate the textboxes with data from server when the page is loaded. Since the cells are always re-initialized as i explained above, whatever I enter in the field goes away and the server data is re populated once i scroll down and come back to the initial stage. This is because the populating the data code is also in the same place. After fetching a reusable cell it populates the data.
Previously till ios6, we used if(cell==nil) and hence we loaded server data inside the cell and when reusing the cell, this piece of code will never be called.
I have other dirty solutions, but would like to know if someone else has a graceful way of dealing this. Please help.
You just don't store any data in the table view cell but in the model that fills the table cell. This is always the way it should be done.
Looking from the MVC standpoint than the UITableViewCell is a view. Since it is reused by iOS you should use a model to the view.
Yes, this is the expected behavior of UITableView. For performance reasons, cells are reused. Thus, it is your responsibility to populate the views in a Table View Cell every time tableView:cellForRowAtIndexPath: is called.
The thing I don't understand from your question - are you making a network call every single time a cell comes into view? If so, cache the results somewhere. Or, if it's a small amount of data, consider just doing it all in one shot at the beginning (still need to be asynchronous though).
One thing I see a lot of developers do is move a lot of code into UITableViewCell subclasses, which sounds like a good idea because it's modular, but makes solutions for problems like this more difficult. Have the Table View Data Source manage the network calls.
If you need some inspiration, look at Apple's LazyTableImages sample.