I'm making a table view that downloads an image for the cell off the web, every time the cell is loaded.
This proves a problem though, as when you scroll down then up again, the cells are reloaded.
Is there any way to stop the unloading of cells, and control how many cells are loaded when the view appears?
I propose that instead of trying to stop your UITableView from reusing cells, which would decrease performance, you store the images you download, using NSCache for example.
NSCache works the same way as a Dictionary - with key value pairs. The main difference however is it incorporates various auto-removal policies, which ensure that it does not use too much of the system’s memory. Here's the documentation on NSCache: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/NSCache_Class/index.html
Using this method: when you're setting up your UITableViewCell, you could check if an image has been downloaded for that NSIndexPath. If an image hasn't been downloaded you download it and add it to the cache once its finished, otherwise you use your cache and get the image from there.
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
I already found entries with that topic on this page and also a website that provides a tutorial for that problem. But nothing worked very well.
The tutorial sad I should double the height of my tableView so cells loaded earlier, but with a higher tableView I never reached the last cells.
My problem is, I use a tableView to display my apps mainpage. This mainPage shows every time a topic and if its necessary for that topic it shows for example a cell with a map, one with pictures and sometimes not. Problem now, if I trying to scroll to my view its always lagging because it loads a map or this pictures. And scrolling back again because the loaded cells already deleted. I used a tableView because of the possibility to switch celltypes(mapCell, pictureCell, textCell) on and off.
I understand this feature, because of memory issues but in my case its not that much and it would be better if all my cells be preloaded and stay in memory until I change the topic.
Is there a swifty way to told my tableView to have this behavior?
Thanks a lot and nice greetings
I would suggest a different approach. Set up your table view controller to install placeholder images in your cells, trigger an async download that you cache to disk, and then update the cell with it's downloaded content if it's still visible when the download is finished.
There are various third party frameworks that do all this housekeeping for you.
Declare a cell array.
Follow these steps whenever change in your topic.
Clear you array
Create and configure all cells add to your array.
Return these cells to datasource methods using row index. Don't use tableview dequeue method here.
I would like to have my UITableView load 3 or 4 cells outside of the table so that any data to be shown there is already loaded when I scroll down.
I have some images, and data that must be downloaded before it can be shown in the cell.
This causes a visible delay before the images are loaded when scrolling.
I can manually trigger loading of this data by doing it in the UITableViewDataSource tableView:cellForRowAtIndexPath: method. I've done this before, but I'm curious if there's an easier way to do it.
Is there any way to expand the reusable cell pool, or adjust how the cells are loaded/recycled?
EDIT:
To clarify, I have lazy loading of images and data in place.
Everything works fine, I just wanted an easy solution to the "prefetching" problem.
Which can be solved in many ways that has nothing to do with the view itself. Right now you can easily see the images load as you scroll. I just wanted them to load right before they become visible.
You may be looking for an asynchronous table view that loads the data asynchronously.
Apple provides a sample app demonstrating this:
LazyTableImages
Of course, you could pre-cache the data and begin downloading data into your datasource before they scroll.
The general idea is that you are loading data into a datasource (that is separate from the UI), so you can do this at any time (and in the background). You can display temporary data or some type of loading image or spinner if the data isn't loaded yet.
If data of the cell will be loaded when cel becomes visible, you can programmatically scroll the table view by scrolling to the bottom cell and go back to the first cell without animation. Another way would be creating all the cells and placing them into array when your view controller is created, and feed the table from that array that contains already created cells. I think there is no way to extend the cell pool as you are asking. Hope this helps, Good Luck!
I'm trying to create a custom view that can display a large amount of data, similar to the way UITableView is able to display many rows of data. Right now I'm displaying the data in a UIScrollView, but after I reload the data several times my app starts to receive memory warnings.
What's the right way to design a custom view to display a large amount of data, and how can I avoid these memory warnings?
It doesn't. UITableView loads only as many rows as it can fit on the screen. When it needs more rows it asks the data source for them. This gives the appearance that the table contains all the data without all the overhead of copying everything at once.
When your tableView cell is too heavy means, it has imageView and more than one text label better create a custom cell by subclassing the UITableView.
Make sure that in custom cell the subview should be added only once and reused for cells. Just you need to sent the image of imageview or text of label. Even if you have so many cells in tableview the performance will be good.
And also make sure that if you are loading images from server don't fetch every time when you are setting image for cell. use image cache.
The following link might me useful for loading data to table view from server.
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
If we fetch a small image (say, 60 x 60) from the Internet, inside of the method:
-(UITableViewCell *) tableView:(UITableView *) tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath { ... }
What if there are a fixed 25 number of rows vs if the length can be variable (say, 100, or can be 1000)? If we use dequeueReusableCellWithIdentifier to "reuse" a cell, and we probably will need to remove the subviews in this cell (one of which is the 60 x 60 image), and so when the user scroll up and down the list, the cells are reused, and images are re-fetched from the net, and it can be pausing here and there while scrolling.
But if I remove the dequeueReusableCellWithIdentifier and always allocate a new cell:
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:TableViewCellIdentifier];
then when I scroll down and up again, the NSLog shows that the images are still being fetched. Is there a way to prevent that, if we don't want to use an array separately to store the fetched images?
(right now, the behavior is, initially, about 7 images are fetched, and if the table is scrolled down 3 rows, then 3 images are fetched, and if the table is scrolled back up 3 rows, then 3 images are fetched again)
you should have an image caching mechanism. download the image if it does not exist. Set an expiration where you will need to refetch it. Then display it.
Next time you load it. you display the already downloaded image and check the expiration. If its expired you download it again, and update the display.
pretty simple.
Only complication is, after the download should the image be over written. I test this with a GUID. When the image download is requested I put a UUID in the tableview or image object itself. I also send that UUID along with the download task. when the download returns you test the download's UUID against the display item's UUID. if they are Equal. then you update the display. if they have changed, then another download was requested and overrode yours. Therefore the images was changed by that process and a download will be finishing soon that does have that UUID and will change the image with its downloaded contents.
Hope that helps :)
You could use image caching as Volure mentioned. I also suggest you implement some form of lazy loading for those images. There are many different implementations for this. This project is a subclass of UIImageView that supports asynchronous loading.
Also, take a look at this question, the answers could be helpful.
hope this helps.
As far as the scroll view pausing your should load your images (if they are not cached or need updating as per DarkAngel's answer) on another thread. I'd use NSOperationQueue.
Here's a good tutorial on setting up NSOperationQueue. A good idea is to use a shared queue in your app delegate that you can just add tasks to when you need it.
Just use SDWebImage or AFNetworking. They both offer a category of UIImageView giving you the caching mechanism.
What that means is if the image downloader library (SDWebImage or AFNetworking) has found an image in its cache, then it will just get that one from disk rather than redownload the image from the web.
You're seeing that lag is most likely because in your cellForRowAtIndexPath: table view delegate method, you're probably downloading the thumbnail image using NSURLConnection on the main thread.
SDWebImage or AFNetworking will essentially do all the hard work for you and make your table responsive. All that you have to do is really:
[myThumbnailImageView imageWithURL:imageURL placeholder:[UIImage imageNamed:#"myPlaceholderImage.png"]];
Once you got that line in there, that's it. Your real image will appear once its downloaded, until then you can continue to smoothly scroll the table view. Each cell will display a placeholder image that you specified (see above line of code) when the image has not been downloaded.
For More convenience to download image use EGOImageLoader/EGOImageView Download this class by this link https://github.com/enormego/EGOImageLoading.
From this EGOCache is used to store your image. First time you download, while scrolling the UITableview get the image from EGOCache store. In that class it store the Downloaded image with key. The key is hashed value of your imageUrl. Use same key to retrieve your image from cache. Tamilarasan