I'm using SDWebImage to download and cache images in an UITableView asynchronously but I'm having some problems.
The scenario is the following:
When the cell is created I want to load a low quality blurred image (1-2kb) from an URL, before the real image comes in. After the higher quality image is downloaded, I want to show that one.
Until now, I've tried these options, but neither one seems to work the way I expect:
1:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:[NSURL URLWithString:lowQualityImageURL]
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (finished) {
cell.pictureView.image = image;
[manager downloadImageWithURL:[NSURL URLWithString:highQualityImageURL]
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image && finished) {
[UIView transitionWithView:cell.pictureView
duration:0.1f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
cell.pictureView.image = image;
} completion:^(BOOL finished) {
}];
}
}];
}
}];
When this code is used, the low quality image seems to be downloaded and showed before the real image, but if the user scrolls fast in the table, he will end up with wrong images for some cells (because of cell reusing - I guess). - Any solution here?
2:
[cell.pictureView sd_setImageWithURL:[NSURL URLWithString:lowQualityImageURL] placeholderImage:[UIImage new] options:SDWebImageRefreshCached completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
[cell.pictureView sd_setImageWithURL:[NSURL URLWithString:highQualityImageURL] placeholderImage:image options:SDWebImageRefreshCached completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
}];
}];
This approach seems to fix the "wrong image for cell" issue, but most of the cells end up showing their corresponding blurred image istead of the high quality image which is supposed to show.
So, to sum up, do you have an advice about how can I achieve the result I want? (download low quality image -> show it until the high quality image is downloaded -> replace it with high quality image)
LE: When using the 2nd approach I see a weird behaviour. Console output:
thumb set for cell: 1
real image set for cell: 1
thumb set for cell: 2
thumb set for cell: 3
thumb set for cell: 0
real image set for cell: 0
It appears that somehow, some of the downloads get cancelled.
I ended up using two UIImageViews, one for each photo, overlapped. When the real (big) image is completely downloaded I smoothly fade the blurred one.
Here's a helper function I wrote in Swift using Nuke:
func loadImageWithTwoQualities(lowQualityURL: NSURL, highQualityURL: NSURL, completion: (UIImage) -> () ) {
Nuke.taskWith(lowQualityURL) {
completion($0.image!)
Nuke.taskWith(highQualityURL) {
completion($0.image!)
}.resume()
}.resume()
}
You can use it with any type of image view like so:
let normalImageUrl = NSURL(string: picture)!
let largeImageUrl = NSURL(string: picture + "?type=large")!
loadImageWithTwoQualities(normalImageUrl, highQualityURL: largeImageUrl) { image in
self.someUIButton.setBackgroundImage(image, forState: .Normal)
}
Related
Im using SDWebImage code, to download an image from internet.
GitHub SDWebImage
Im Doing this in Xcode 8 and Swift 3.
I want to resize it once it is downloaded, but I can't have image size until I Download the image. (To make it resize proportionally)
So, my question is, is there a way to have a completion block to execute some code when the image has finish being downloaded? I can`t seem to find on github project info about the completion of the download.
Thanks!
I Found it! Post it in case someone else needs it.
I used this to load from web
imgView.sd_setImage(with: URL(string: news.imageURL))
But for load and have a completing block I need to do:
imgView.sd_setImage(with: URL(string: news.imageURL)) { (image, error, cache, url) in
// Your code inside completion block
}
Here's some code right out of one of the SDWebImage example files, you can find more examples here: https://github.com/rs/SDWebImage/tree/master/Examples
Swift Version:
imageView.sd_setImageWithURL(NSURL(string: urlString), completed: {
(image, error, cacheType, url) in
// your code
})
Objective-C Version:
- (void)configureView
{
if (self.imageURL) {
__block UIActivityIndicatorView *activityIndicator;
__weak UIImageView *weakImageView = self.imageView;
[self.imageView sd_setImageWithURL:self.imageURL
placeholderImage:nil
options:SDWebImageProgressiveDownload
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL *targetURL) {
if (!activityIndicator) {
[weakImageView addSubview:activityIndicator = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]];
activityIndicator.center = weakImageView.center;
[activityIndicator startAnimating];
}
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}];
}
}
Hope this helps.
I would like to ask on how to get the downloaded image after the SDWebImageManager downloaded it. I have only the code to download it via URL, here's what I got:
let manager: SDWebImageManager = SDWebImageManager.sharedManager()
manager.downloadImageWithURL(NSURL(string: feedDetails.thumbnail), options: [],
progress: {(receivedSize: Int, expectedSize: Int) -> Void in
print(receivedSize)
},
completed: {(image, error, cached, finished, url) -> Void in
self.feedImage.image = image
}
)
As far as I know (I just looked up the author's Git page) there is the following method to directly access an image which is stored inside the cache-
You can use the SDImageCache to store an image explicitly to the cache with the following code:
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
Where myImage is the image you want to store and myCacheKey is a unique identifier for the image.
After you stored an image to the cache and want to use that image, just do the following:
[[SDImageCache sharedImageCache] queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
// image is not nil if image was found
}];
This code is Objective-C code, you have to "convert" it to swift yourself.
I hope I could help you!
From the SDWebImageManager class the downloadImageWithURL: method
Downloads the image at the given URL if not present in cache or return
the cached version otherwise.
So if the image is present in cache you are already retrieving it with your code, instead of downloading from the web.
Thanks for answer #beeef but SDWebImage has been updated some part of code:
Save image:
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:string] options:SDWebImageDownloaderUseNSURLCache progress:nil completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// Cache image to disk or memory
[[SDImageCache sharedImageCache] storeImage:image forKey:#"img_key" toDisk:YES completion:^{
//save
}];
}
}];
Get image from disk cache:
[[SDImageCache sharedImageCache] queryCacheOperationForKey:#"img_key" done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
[self.imageV setImage: image];
}];
I tried to load an image progressively in my app. Basically what I tried is while the image is loading, I want to show the fully loaded image from blurred state. I tried,
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:[NSURL URLWithString:#"https://profile.microsoft.com/RegsysProfileCenter/Images/personal_info.jpg"]
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
[self.profileBtn setImage:[UIImage imageWithData:[NSData dataWithBytes:&receivedSize length:sizeof(receivedSize)] scale:15] forState:UIControlStateNormal];
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// [self.profileBtn setImage:image forState:UIControlStateNormal];
}
}];
Is it possible to load an image progressively using SDWebImage. Please help me with this.
You can try this implementation
+ (void)loadImageWithURLString:(NSString *)urlString forImageView:(UIImageView *)imageView {
[imageView sd_setImageWithURL:[Utilities stringToURL:urlString]
placeholderImage:[UIImage placeHolderImage]
options:SDWebImageProgressiveDownload];
}
The option says that
/**
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
* By default, the image is only displayed once completely downloaded.
*/
SDWebImageProgressiveDownload = 1 << 3,
You can show percentage of downloading on temp label or below activity indicator (can show activityindicator on image view or button till download is not completed) like in progress block,
NSInteger downloadCompleted = (receivedSize/ expectedSize) * 100;
label.text = downloadCompleted; //temp label on image view or below activityindicator
Hope this will help :)
From my web service i am getting multiple sized images. I m loading them in TableView. When i m loading them new images which haven't cached yet flickers between placeholder and original image, or sometimes the image appear as smaller than the usual size. But just a bit scrolling down or up actually fix the problem but i want them to be at original size from the beginning. But it would appear in original shape from the next time i suppose because the picture was already cached
Initially
After a bit scrolling
My code in cellForRowAtIndexPath:
[cell.image sd_setImageWithURL:[NSURL URLWithString:[NSMutableString stringWithFormat:#"%#app/media/access/pictures?p=%#",baseurl,data.picPath]]
placeholderImage:[UIImage imageNamed: #"image_loader.gif"]];
then in heightForRowAtIndexPath:
NSURL *filePath = [NSURL URLWithString:[NSMutableString stringWithFormat:#"%#app/media/access/pictures?p=%#",baseurl,data.picPath]];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:filePath];
UIImage *image = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key];
self.img = image;
if (self.img.size.width > CGRectGetWidth(self.view.bounds)) {
CGFloat ratio = self.img.size.height / self.img.size.width;
return CGRectGetWidth(self.view.bounds) * ratio+140;
} else {
return self.img.size.height+180;
}
I have visited https://github.com/rs/SDWebImage and in their common problem section they have mentioned about the problem but the suggested solution didn't work for me!!!
Actually what i did, i checked if the image was cached already and if not i have downloaded it asynchronously under heightForRowAtIndexPath:
if(image != NULL)
{
self.img = image;
}
else
{
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
[downloader downloadImageWithURL:filePath
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// do something with image
self.img = image;
}
}];
}
i want to set uiimage to a variable outside the scope of function .i tried the code below but it doesn't work . code below is used in custom uitable view cell class . so i just cannot alloc and init it . as this will cause to initialize it every time cell is displayed/ scrolled
uiimage *img;
[manager downloadWithURL:[NSURL URLWithString:friendData.picUrl] options:SDWebImageRefreshCached progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
img =image; ////this is not working !!
}];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.profileImage setImage:img]; //and off course this wont work too
});
I think you may just need to set __block UIImage image = nil;
You can initialize the image1 with any other initializing methods:
Try this one:
image1 = UIImage.init(CGImage:image.CGImage);
Initializer method will create new object as per this document.
I haven't tried it. Please try if it works. If not working just comment me, I may find other solution.
you need to allocate image1 to make it work out of the function.try this
image1 = [[UIImage alloc] initWithCGImage: image.CGImage];