I have app with TableView and cells. In each cell there is UIImageView.
All images are stored on server.
I can use two different methods to load images. Which of them should prefer and why?
Method A :
Use library like SDWebImage to load image and place it in cellForRowAtIndexPath function. So image will be downloaded when cell is created.
Method B :
When i load JSON with image list from server i can create array of UIImages. In each of them i will asynchronously download image from server. And in cellForRowAtIndexPath function i can just assign one of previously created UIImages to current cell image.
Method A - SDWebimage is best for you.
and solve reuse in tableviewcell check this link : Handling download of image using SDWebImage while reusing UITableViewCell
Don't ever use method 2 to handle Images in your application. This is a data and memory wise expensive method. To the best of my understanding, this method would extensively increase the memory pressure. If you create an array of images that would remain in the memory as long as your view controller stays. As the size of this array increases the situation will get worse.
SDWebImage is far far better approach for this task. It saves the images to local storage once downloaded thus creating a cache of images. So you do not have to download the images again and again.
i wish to use AFNetworking if you don't need to cache image , this tools faster than SDWebImage when load image from server . //////
if you use custom cell => replace UITableViewCell with your name file for cell
-(void)fetchImageFromURL:(NSString*)imageURLString Cell:(UITableViewCell*)cell {
/*
_ This Function will accept the string url for Image
_ Also the cell that have image icon
_ After take paramter will make request to fetch image
_
1- if return image => will show Image
*/
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#", imageURLString]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
__weak UITableViewCell *weakCell = cell;
// get image with request
[[cell imageView ] setImageWithURLRequest:request placeholderImage:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[[weakCell ImageIcon ] setImage:image];
[weakCell setNeedsLayout];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"couldn't load image with url :%#", url);
}];
}
The best choice in my opinion is to rely on a stable, fresh, followed and secure framework.
Recently come to the scene a great framework written in Swift called Nuke (Swift 3 - Xcode 8) . I think you could try Nuke, his power is the "preheat images" (preheating/prefetching means loading images ahead of time in anticipation of its use.), it's full compatible with Alamofire , already knowed by community (raywenderlich.com) and now is at v.4.x (stable and mature). This library have custom handlers and requests with many options.
Related
I have made a custom Tableview which has an image view and some labels that takes values from API.
It's a custom table in which images and labels recieve data from API related to real estate .
When my custom table loads, values start coming from API.
The actual issue ocurr in image view . I have set a local image in an image view , when the data coming from API has no image than it should show local image otherwise data should show its original image submitted by customer in the forms.
Now when data start coming from API first in image view it shows local image than when images start coming from API it start showing that but when we scroll down in image view it shows same images in image view and repeats many times and we cannot identify that which is original image.
I can provide the code if anyone sent me his email , he can get all the senario of work.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier=#"Cell";
CustomTableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
StringData* cust = [_Title objectAtIndex:indexPath.row];
cell.Title.text = cust.title1;
cell.area.text=cust.Area;
cell.city.text=cust.City;
cell.phone.text=cust.phoneno;
cell.price.text=cust.Price;
cell.location1.text=cust.location;
cell.name1.text=cust.dealer_name;
cell.email1.text=cust.dealer_email;
cell.type1.text=cust.property_type;
cell.status1.text=cust.status;
cell.room1.text=cust.rooms;
cell.washrooms.text=cust.bath;
cell.floor1.text=cust.floors;
cell.imagetext.text = cust.images;
tii=cell.Title.text;
NSLog(#"Tittttttle is %#",tii);
NSURL *url = [NSURL URLWithString:cust.images];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
cell.images1.image = image;
});
}
}
}];
[task resume];
return cell;
}
It seems you don't store an image into local application memory. You tried to load image on every time when cell is appear. So you download image every time.
Best Solution:
You need to store image local memory using SDWebImage.
Objective-C:
#import <SDWebImage/UIImageView+WebCache.h>
...
[imageView sd_setImageWithURL:[NSURL URLWithString:#"IMAGE_URL"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Swift:
import SDWebImage
imageView.sd_setImage(with: URL(string: "IMAGE_URL"), placeholderImage: UIImage(named: "placeholder.png"))
If you are loading an image for the first time, then it will download and store an image into local memory. So from second times, it will load from local itself.
Well, its because your cell is getting re-used and the previous image is being displayed. You can override prepareForReuse() method of your tableviewcell subclass or consider setting placeholder for the url image using SDWebImage
Edit: Explaination
You are downloading your image asyc and setting it to your imageview of your tableviewcell, what is happening here is while your next image is being downloaded, before that your cell is re-used and displays previous image.
I have a TableViewCell with an ImageView, and I am setting the image like so:
[self.contentImageView setImageWithURL:thumbnail_url];
The contentMode is set to UIViewContentModeScaleAspectFill, and the subviews are clipped.
This works best for images that are either Portrait or Landscape, but UIViewContentModeScaleToFill actually works better for Landscape images, which are far more plentiful.
So, I want to detect the orientation of the image by comparing the width and height, and change the contentMode accordingly.
At first, I tried to inspect/log the ImageView.image property, directly after I set it from the url, but it show's nil. not sure why exactly...?
Next, I decided to put that NSURL into an NSData object, then create an UIImage from that data, and set the ImageView's image property with that iVar, like so:
NSData *data = [NSData dataWithContentsOfURL:thumbnail_url];
UIImage *image = [UIImage imageWithData:data];
[self.contentImageView setImage:image];
// Get image.size etc.
This - particularly the NSData call - slows down the loading of the TableViewCell's considerably, so I'd like to avoid it.
So, I'm wondering if there is anyway to reach into the un-cropped source image properties of the ImageView before the scaling happens to the contentMode?
The problem here is your misunderstanding of how that first method works. setImageWithURL: is a method from one of the open source image loading libraries. Probably either AFNetworking or SDWebImage.
These methods are asynchronous and return immediately. They download the image on a background queue and then return to the main queue to set up the view with it. You are trying to access the image before it is downloaded. The reason the manual NSData approach is working is because it is synchronous and the main queue is stuck while the images download.
Both libraries I mentioned have separate methods with a callback block on the main queue allowing you to act on the response.
For AFNetworking you can use this method:
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
For SDWebImage you can use this method:
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
In either of these, the callback blocks will let you access the image once it is downloaded (if successful).
You can also make something like this work manually using the dataWithContentsOfURL: approach by using GCD like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
{
NSData *data = [NSData dataWithContentsOfURL:thumbnail_url];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^()
{
[self.contentImageView setImage:image];
});
});
This is a basic pattern used in networking to perform the network request and processing on a background queue before updating the view on the main queue. Keep in mind this particular piece of code is very simplistic and would require more work to work as nicely as the libraries I mentioned above.
I have a collection view, which contains cells in each section, and each cell has an image in it.
Possibly, the collection view holds 20 or more cells.
I want to load the images off the internet into the cells. The issue is that this can take a few seconds, and I want the collectionView to be displayed even if the images have not downloaded completely.
The collection view is given an array that contains the URLs, and so far, I have been downloading off the internet within collection view cellforitematindexpath
However, the view only becomes visible after all the cells have been loaded, and since each call to collection view cellforitematindexpath downloads an image, if 20 images are pulled off of URLs, it takes way to long.
What can I do to display the view, and THEN download the images, and then display them?
Hope I made myself understandable, if not, please ask!
Thanks!
C
Yes, you could use SDWebImage (as mention at comment above).
Another interesting side you could face out, that if image haven't load yet and user scroll view, it could be problems with dequeueReusableCellWithReuseIdentifier:forIndexPath, so it's better to download images throw id <SDWebImageOperation>.
Prepare for reuse will be something like this:
- (void)prepareForReuse
{
[super prepareForReuse];
if (self.imageOperation)
[self.imageOperation cancel];
self.imageOperation = nil;
}
And load will be like this:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
self.imageOperation = [manager downloadWithURL:[NSURL URLWithString:urlString]
options:SDWebImageRetryFailed
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if (image)
{
self.imageView.image = image;
}
}];
I use an UICollectionView to display a lot of images with a batch of 32. Every time the i reach the end of the collection view i load an other batch of 32 images and resize the collectionView contentsize.width to accepte the new items. The loading is made by calling a webservice with AFNetworking.
When i scroll very fast from the start to the end and to the end to the start i receive a EXC_BAD_ACCESS.
It also happend when a reach the end of the CollectionView. It's like it tries to load some attributes that are not already available.
I tried to figure it out since 1 day without any success. I tried with instrument / NSZombie enabled/ guardmalloc ...
EDIT
There is also a very strange think: This bad access only appeared when i replaced PSTCollectionView with the real UICollectionView. So to be sure i just made de inverse move and replace UICollectionView with PSTCollectionView and the badaccess disappeared.
I'm totaly lost :)
END EDIT
I'm using both arc and non arc files in the project.
The only think i'm able to spot is this stack trace :
Your help will be more than Welcome.
Blockquote
The issue may be that the images are just too big. What you may want to do is resize the image from the downloaded image before setting the image property on each UIImageView.
Bill Dudney published a very useful iBook on the topic which goes into detail on the consequences of images on memory and how to optimize for it. It is $4.99 and very helpful.
https://itunes.apple.com/us/book/all-image-io-you-need-to-know/id601759073?mt=11
The following method will resize an image to the given size which will decrease the memory footprint and hopefully prevent your issue.
- (UIImage *)resizeImage:(UIImage *)image size:(CGSize)size {
UIGraphicsBeginImageContext(size);
[image drawInRect: CGRectMake(0, 0, width, height)];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage;
}
Now if you are using AFNetworking to set the image directly from the URL using the AFNetworking category you may want to use the alternate method so you can intervene and resize the image. The code below will do that.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:imageURL];
[request addValue:#"image/*" forHTTPHeaderField:#"Accept"];
[imageView setImageWithURLRequest:request placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// resize image
// set image on imageView
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
// handle error
}];
First of all this is not a duplicate. I have seen some identical questions but they didn't help me as my problem varies a little bit.
Using the following code i am download the images asynchronously in my project.
{
NSURL *imageURL = [NSURL URLWithString:imageURLString];
[self downloadThumbnails:imageURL];
}
- (void) downloadThumbnails:(NSURL *)finalUrl
{
dispatch_group_async(((RSSChannel *)self.parentParserDelegate).imageDownloadGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *tempData = [NSData dataWithContentsOfURL:finalUrl];
dispatch_async(dispatch_get_main_queue(), ^{
thumbnail = [UIImage imageWithData:tempData];
});
});
}
Due to the logic of the program, i have used the above code in files other than the tableview controller which is showing all the data after getting it from the web service.
PROBLEM: On screen images does not show up until i scroll. The off screen images are refreshed first. What can i do to solve my problem.
Apple's lazy loading project is using scrollViewDidEndDragging and scrollViewDidEndDecelerating to load the images but the project is way too big to understand plus my code is in files other than the tableview controller.
NOTE: Kindly do not recommend third party libraries like SDWebImage etc.
UPDATE: As most of people are unable to get the problem, i must clarify that this problem is not associated with downloading, caching and re-loading the images in tableview. So kindly do not recommend third party libraries. The problem is that images are only showing when the user scrolls the tableview instead of loading the on screen ones.
Thanks in advance
I think what you have to do is:
display some placeholder image in your table cell while the image is being downloaded (otherwise your table will look empty);
when the downloaded image is there, send a refresh message to your table.
For 2, you have two approaches:
easy one: send reloadData to your table view (and check performance of your app);
send your table view the message:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Using reloadRowsAtIndexPaths is much better, but it will require you to keep track of which image is associated to which table row.
Keep in mind that if you use Core Data to store your images, then this workflow would be made much much easier by integrating NSFetchedResultController with your table view. See here for an example.
Again another approach would be using KVO:
declare this observe method in ItemsViewCell:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqual:#"thumbnail"]) {
UIImage* newImage = [change objectForKey:NSKeyValueChangeNewKey];
if (newImage != (id)[NSNull null]) {
self.thumbContainer.image = newImage;
[self.thumbContainer setNeedsLayout];
}
}
}
then, when you configure the cell do:
RSSItem *item = [[channel items] objectAtIndex:[indexPath row]];
cell.titleLabel.text = [item title];
cell.thumbContainer.image = [item thumbnail];
[item addObserver:cell forKeyPath:#"thumbnail" options:NSKeyValueObservingOptionNew context:NULL];
By doing this, cell will be notified whenever the given item "thumbnail" keypath changes.
Another necessary change is doing the assignment like this:
self.thumbnail = [UIImage imageWithData:tempData];
(i.e., using self.).
ANOTHER EDIT:
I wanted to download and load the images just like in the LazyTableImages example by Apple. When its not decelerating and dragging, then only onscreen images are loaded, not all images are loaded at once.
I suspect we are talking different problems here.
I thought your issue here was that the downloaded images were not displayed correctly (if you do not scroll the table). This is what I understand from your question and my suggestion fixes that issue.
As to lazy loading, there is some kind of mismatch between what you are doing (downloading the whole feed and then archiving it as a whole) and what you would like (lazy loading). The two things do not match together, so you should rethink what you are doing.
Besides this, if you want lazy loading of images, you could follow these steps:
do not load the image in parser:foundCDATA:, just store the image URL;
start downloading the image in tableView:cellForRowAtIndexPath: (if you know the URL, you can use dataWithContentOfURL as you are doing on a separate thread);
the code I posted above will make the table update when the image is there;
at first, do not worry about scrolling/dragging, just make 1-2-3 work;
once it works, use the scrolling/dragging delegate to prevent the image from being downloaded (point 2) during scrolling/dragging; you can add a flag to your table view and make tableView:cellForRowAtIndexPath: download the image only if the flag says "no scrolling/dragging".
I hope this is enough for you to get to the end result. I will not write code for this, since it is pretty trivial.
PS: if you lazy load the images, your feed will be stored on disk without the images; you could as well remove the CGD group and CGD wait. as I said, there is not way out of this: you cannot do lazy loading and at the same time archive the images with the feed (unless each time you get a new image you archive the whole feed). you should find another way to cache the images.
Try using SDWebImage, it's great for using images from the web in UITableViews and handles most of the work for you.
The best idea is caching the image and use them. I have written the code for table view.
Image on top of Cell
It is a great solution.
Try downloading images using this code,
- (void) downloadThumbnails:(NSURL *)finalUrl
{
NSURLRequest* request = [NSURLRequest requestWithURL:finalUrl];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data, NSError * error)
{
if (!error)
{
thumbnail = [UIImage imageWithData:data];
}
}];
}