Objective C: For loop is finished before image is finished - ios

I have an arrayOfLinks that contains a lot of url links. I need to get images from these links. I am using the following code to do so.
- (void)getImages {
NSArray *links = arrayOfLinks;
for (NSString *link in links ) {
[self.picImage sd_setImageWithURL:[NSURL URLWithString:link] placeholderImage:nil options:SDWebImageHighPriority completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(#"pic image set finished");
}];
}
[self finishSettingImages];
NSLog(#"for loop finished");
}
This almost works, but the problem is the for loop finishes before the images are set. I want to call the method finishSettingImages after each image is set. What would be the best way to do this?
Edit: I want the method 'finishSettingImages' called after ALL the images are set. Sorry for not clarifying this.

Just use GCD groups.
NSArray *links = arrayOfLinks;
dispatch_group_t group = dispatch_group_create();
for (NSString *link in links) {
dispatch_group_enter(group);
[self.picImage sd_setImageWithURL:[NSURL URLWithString:link] placeholderImage:nil options:SDWebImageHighPriority completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
dispatch_group_leave(group);
}];
}
// you can use dispatch_get_main_queue() or background here
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// all is done
});
If you only need to get images, you can use SDWebImageManager:
NSArray *links = arrayOfLinks;
__block NSMutableArray *images = [NSMutableArray new];
dispatch_group_t group = dispatch_group_create();
for (NSString *link in links) {
dispatch_group_enter(group);
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:[NSURL URLWithString:link]
options:SDWebImageRetryFailed
progress:^(NSInteger receivedSize, NSInteger expectedSize) {}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
[images addObject:image];
}
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// all is done
for (UIImage *image in images) {
//do something with images
}
});
Code example for Swift 3/4:
var arrayOfLinks = ... // you string links are here
let dispatchGroup = DispatchGroup()
for link in arrayOfLinks {
if let url = URL(string: link) {
dispatchGroup.enter()
self.imageView.sd_setImage(with: url) { (image, error, cacheType, url) in
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
print("all is done ")
}

EDIT: this answer is bad, as Josh in the comments pointed out, I failed to see the race condition with the index == total check
Try this:
- (void)getImages
{
NSArray *links = arrayOfLinks;
NSUInteger total = [links count];
__block NSUInteger index = 0;
__weak id *weakSelf = self; //if your picImage retains the completion block as a property,
//then you will want to capture a weak reference to self, so you don't have a retain cycle
for (NSString *link in links ) {
[self.picImage sd_setImageWithURL:[NSURL URLWithString:link] placeholderImage:nil options:SDWebImageHighPriority completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(#"pic image set finished");
index += 1;
if( index == total )
{
//again if you don't store this block as a property, you can just reference self instead of weakSelf
[weakSelf finishSettingImages];
}
}];
}
NSLog(#"for loop finished");
}
the __block attribute makes the block use a pointer to the variable so that it can modify the value across executions of the block. That's how we can increment the index variable. total is not __block so the current state of total when you define the block is what's going to be used every time the block executes. So even if you set total to 100 from 10 within the block, the next time the block executes it will still see total as 10.

Related

Dynamic image height in tableview with using sdwebimage

I want dynamic height image show in tableview, image is come from url so I am using the sdwebimage.
Sdwebimage set image in background thread.
[self.imageView sd_setImageWithURL:[NSURL URLWithString: encodedurl1] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
}];
I am update the constraint height of image view in this thread.
[self.imageView sd_setImageWithURL:[NSURL URLWithString: encodedurl1] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
CGFloat multiplier = (CGRectGetWidth(self.imageView.bounds) / image.size.width);
[self.imageViewHeightConstraint setConstant:multiplier * image.size.height];
}];
This code is helpful for me but I get image size in background thread
So, I take a time to update the height of constraint image view
I am search and goggling last 2 days.
This type issue get many developer, but not answer in any question in stack overflow.
do the update in main thread like
[self.imageView sd_setImageWithURL:[NSURL URLWithString: encodedurl1] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
dispatch_async(dispatch_get_main_queue(), ^{
CGFloat multiplier = (CGRectGetWidth(self.imageView.bounds) / image.size.width);
[self.imageViewHeightConstraint setConstant:multiplier * image.size.height];
[self setNeedsUpdateConstraints];
});
}];
Try this Helper method which created for my project.
-(void)imageWithURL:(NSURL *)imageurl WithIndexPath:(NSIndexPath *)indexpath WithCallback:(void(^)(UIImage *downloadedImage,BOOL isCache , NSIndexPath *imageIndexPath))callback{
if (![[SDWebImageManager sharedManager ]diskImageExistsForURL:imageurl]) {
[[SDWebImageManager sharedManager]downloadImageWithURL:imageurl options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// resize image here
callback(image , NO,indexpath);
}];
}
else{
UIImage *image = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:[imageurl absoluteString]] ;
// resize image here
callback(image, YES ,indexpath);
}
}
Call this method in cellForRowAtIndexPath
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self imageWithURL:[NSURL URLWithString:objPreparations.strImageURL] WithIndexPath:indexPath WithCallback:^(UIImage *downloadedImage, BOOL isCache, NSIndexPath *imageIndexPath) {
if (downloadedImage) {
dispatch_async( dispatch_get_main_queue(), ^{
UITableViewCell *existcell = [tableView cellForRowAtIndexPath:imageIndexPath];
if (existcell) {
// assign image to cell here
[tableView reloadRowsAtIndexPaths:#[imageIndexPath] withRowAnimation:UITableViewRowAnimationNone];
}
});
}
}];
});
Dont Forgot
In viewDidLoad
tblView.estimatedRowHeight = 44.0 ;
tblView.rowHeight = UITableViewAutomaticDimension;
And also Give top,bottom,leading,trailing constraints to imageview and greater than equal height constraint to imageview

SDWebimage to create folder (or group images)

If I just download like this, it store in default folder.
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:[NSURL URLWithString:urlStr]
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image)
{
}
}];
How can I group images into different folder? I saw initWithNamespace but I don't know how to use. If I use that method, how can I refer to default folder again also?

SDWebImageManager image not loaded into TableViewController

I am using SDWebImageImagemanger to hadnle asyn call and caching. However it does not return the image to the cell.imageview.view. Here is the code:
NSString *imageURLString=[[tableData objectAtIndex:indexPath.row] valueForKey:#"picture"];
NSURL *url = [NSURL URLWithString:imageURLString];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:url
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
cell.imageView.image = image;
}
}];
You should use it this way:
NSString* imageURL = [[tableData objectAtIndex:indexPath.row] valueForKey:#"picture"];
[cell.imageView setImageWithURL:[NSURL URLWithString:imageURL]];
For that, first import the category:
#import <SDWebImage/UIImageView+WebCache.h>
Please note that you don't use the manager at all, you just use the UIImageView+WebCache category provided by the library, which gives you the setImageWithURL: method. There are other variants of the method which allows you to keep track of the download process, a completion handler, etc.

How to use SDWebImage in UIWebview

Like we set image in UIImageview using SDWebImage
[imageview.setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {... completion code here ...}];
Is there any way to use SDWebImage in UIWebview in <img> tag
NSString *htmlString =#"<html lang=\"en\"><img src='http://cdn.tutsplus.com/mobile/uploads/legacy/iOS-SDK_UIView-Animation/Animate-Icon.png' /> </div><div id=\"content\"><div>BlahBlahBlah LoremIpsum</div><br></body>"
[WebView loadHTMLString:descriptionHT baseURL:nil];
Thanks in advance :)
First cache your image:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:#"http://cdn.tutsplus.com/mobile/uploads/legacy/iOS-SDK_UIView-Animation/Animate-Icon.png"]
options:
0 progress : ^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:
^(UIImage * image, NSError * error, SDImageCacheType cacheType, BOOL finished) {
if (image && finished) {
[[SDImageCache sharedImageCache] storeImage:image forKey:#"icon"];
}
}];
Then when you need the image
__block NSString *imageSource;
__block NSData *imageData = [NSData data];
SDImageCache *imageCache = [SDImageCache sharedImageCache];
[imageCache queryDiskCacheForKey:#"icon" done:^(UIImage * image, SDImageCacheType cacheType) {
imageData = UIImagePNGRepresentation(image);
if (image) {
imageSource = [NSString stringWithFormat:#"data:image/png;base64,%#", [imageData base64Encoding]];
} else {
//start download of image
}
}];
Then in your html:
NSString *htmlString = [NSString stringWithFormat:#"<html lang=\"en\"><img src=\"%#\"/> </div><div id=\"content\"><div>BlahBlahBlah LoremIpsum</div><br></body>", imageSource];
[WebView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
You don't have to use a key when caching the image, I just thought it made things a little easier.

Saved image data to core data but launches simulator again, it's gone

I have saved image data to core data, the key is binary data format. After I saved to the core data, I displaying the image in a UIImageView using valueForKey: . But After I launch the simulator again, everything is still saved, but the image data is gone, when I NSLog the key's data description, it's null.
NSDictionary *show = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:nil];
Summary *summary = [NSEntityDescription insertNewObjectForEntityForName:#"Summary" inManagedObjectContext:weakSelf.managedObjectContext];
summary.title = show[#"title"];
summary.poster = show[#"images"][#"poster"];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:summary.poster] options:SDWebImageProgressiveDownload progress:^(NSUInteger receivedSize, long long expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
NSData *data = UIImageJPEGRepresentation(image, 0.5);
summary.posterImageData = data;
}];
[weakSelf.managedObjectContext dct_saveWithCompletionHandler:^(BOOL success, NSError *error) {
if (success) {
} else {
NSLog(#"%#", error);
}
}];
I think your current code, the downloadWithURL's completed block is being called after dct_saveWithCompletionHandler , try:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:summary.poster] options:SDWebImageProgressiveDownload progress:^(NSUInteger receivedSize, long long expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
NSData *data = UIImageJPEGRepresentation(image, 0.5);
summary.posterImageData = data;
[weakSelf.managedObjectContext dct_saveWithCompletionHandler:^(BOOL success, NSError *error) {
if (success) {
} else {
NSLog(#"%#", error);
}
}];
}];
Make sure that you're actually saving the managed object context. Doublecheck that you've set the concurrency type for your MOCs correctly, too.

Resources