Progress Bar updates out of synch with image downloads - ios

I have an iPad app where I download a list of images and a progress bar updates as the images are downloaded. I can't seem to get the progress bar and image downloads to match up. The progress bar always finishes before the image downloads are completed. I have a method UpdateProgressBar that should increment the progress bar every time an image is downloaded.
-(void)DownloadPhoto{
NSMutableArray *failedDownloads = [[NSMutableArray alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
listPhoto = [CoreDataRead GetPhotoList:[self selectedAccountGuid]];
dispatch_group_t downloadGroup = dispatch_group_create();
for (Photo *item in listPhoto) {
NSString *imageName = item.photoName;
NSString *myURL = [NSString stringWithFormat:#"%#%#", #"http://acimagedownload.com/photos/", imageName];
NSURL *url = [NSURL URLWithString:myURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_group_enter(downloadGroup);
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError == nil && data != nil)
{
if (data != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:#"%#%#",item.guid, #".png"]];
[data writeToFile:path atomically:YES];
NSLog(#"Photo Downloaded %#!", #"");
}
else
{
NSLog(#"image is not downloaded");
}
}
else if (connectionError != nil)
{
[failedDownloads addObject:myURL];
NSLog(#"Error %#",[connectionError description]);
}
else
{
[failedDownloads addObject:myURL];
NSLog(#"Image Download Failed %#!", #"");
}
dispatch_group_leave(downloadGroup);
dispatch_async(dispatch_get_main_queue(), ^{
[self UpdateProgressBar];
});
}];
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self DownloadVideo];
});
});
}
-(void)UpdateProgressBar{
currentTask = currentTask + 1;
NSLog(#"Current Task %#!", [#(currentTask) stringValue]);
float progressPercentage = (float)currentTask/(float)taskCount;
[self.progressBar setProgress:progressPercentage animated:YES];
if(currentTask == taskCount){
[self ShowDoneAlert];
}
}

It's hard to tell without seeing the updateProgressBar method why you get that behaviour but one thing that I noticed is that your dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); is inside your loop.
So what happens is, you go into the first iteration of the loop and at the end of it, the thread execution stops and waits for the first dispatch_group_leave. After that is continues to the next iteration and so on.
If this is the desired behaviour (but I doubt it, I think you actually want your downloads to run in parallel) you should probably use a dispatch_semaphore_t or even a serial queue.

Related

How to show Image in UIImageView from cache using AFNetworking?

Here is my code how to download image from URL and save it into document directory using AFNetworking.
Now, my question is if image is already downloaded from URL then image is loaded from cache instead of re-download it. I want to do this using AFNetworking. I know that the solution for this problem is inside #import "UIKit+AFNetworking/UIKit+AFNetworking.h"
If anyone have any idea of how to help, please help me solve my issue.
#import "ViewController.h"
#define URL #"https://upload.wikimedia.org/wikipedia/commons/e/ec/USA-NYC-American_Museum_of_Natural_History.JPG"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.progressBar.hidden = YES ;
self.lblProgressStatus.hidden = YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)Action:(UIButton *)sender
{
self.progressBar.hidden = NO ;
self.lblProgressStatus.hidden = NO ;
self.ActionDownload.enabled = NO ;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *strURL = [NSURL URLWithString:URL];
NSURLRequest *request = [NSURLRequest requestWithURL:strURL];
NSProgress *progress;
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
}
completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
[self.progressBar setHidden:YES];
self.lblProgressStatus.text = #"Download completed" ;
NSLog(#"File downloaded to: %#", filePath);
NSString * strTemp = [NSString stringWithFormat:#"%#", filePath];
NSArray *components = [strTemp componentsSeparatedByString:#"/"];
id obj = [components lastObject];
NSLog(#"%#", obj);
NSString *docPath = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
NSString *strFilePath = [NSString stringWithFormat:#"%#/%#",docPath, obj];
BOOL fileExists=[[NSFileManager defaultManager] fileExistsAtPath:strFilePath];
if (!fileExists)
{
NSLog(#"File Not Found");
}
else
{
UIImage * image = [UIImage imageWithContentsOfFile:strFilePath];
self.imageView.image = image ;
}
[progress removeObserver:self forKeyPath:#"fractionCompleted" context:NULL];
}];
[self.progressBar setProgressWithDownloadProgressOfTask:downloadTask animated:YES];
[downloadTask resume];
[progress addObserver:self
forKeyPath:NSStringFromSelector(#selector(fractionCompleted)) options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"fractionCompleted"])
{
NSProgress *progress = (NSProgress *)object;
int temp = progress.fractionCompleted * 100 ;
// NSLog(#"%d", temp);
NSString * strTemp = #"%";
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
self.lblProgressStatus.text = [NSString stringWithFormat:#"%d %#", temp, strTemp];
});
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#end
You can download the image using this method defined in UIImageView+AFNetworking:
[imageView setImageWithURL:[NSURL URLWithString:URL] placeholderImage:[UIImage imageNamed:#"placeholder-avatar"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
if ([[extension lowercaseString] isEqualToString:#"png"]) {
[UIImagePNGRepresentation(image) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"png"]] options:NSAtomicWrite error:nil];
} else if ([[extension lowercaseString] isEqualToString:#"jpg"] || [[extension lowercaseString] isEqualToString:#"jpeg"]) {
[UIImageJPEGRepresentation(image, 1.0) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"jpg"]] options:NSAtomicWrite error:nil];
}
} failure:NULL];
The success block will be called even if it gets the image from cache. Hope it helped!
It uses cache by default. To test, go to a url you have access to of an image, then delete the image, and load again, and you'll see it's cached :D The images sometimes are not cached if they're big images.
If you want to increase this cache size, put this in your app delegate:
[[NSURLCache sharedURLCache] setMemoryCapacity:(20*1024*1024)];
[[NSURLCache sharedURLCache] setDiskCapacity:(200*1024*1024)];
EDIT RE: comments:
If you're looking to only download images once to your documents path, then perhaps the best way to test if an image already exists and should be downloaded or not is a test you can create. E.g, if the last path component (the last part of an image file path) of an image exists already in your documents, don't download it, else download it.
EDIT: further comments
Inside UIKit+AFNetworking/UIImageView+AFNetworking.h
/**
Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
By default, URL requests have a Accept header field value of "image / *", a cache policy of NSURLCacheStorageAllowed and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use setImageWithURLRequest:placeholderImage:success:failure:
#param url The URL used for the image request.
*/
- (void)setImageWithURL:(NSURL *)url;
This looks exactly like what you're looking for
to use:
#import <AFNetworking/UIKit+AFNetworking.h>
and use
NSURL *strURL = [NSURL URLWithString:#"http://www.example.com/image.jpg"];
[imageview setImageWithURL:strURL];
I recommend you to use this library https://github.com/rs/SDWebImage
So, you can do something like this:
- (void)loadImage:(NSURL *)url
{
__block UIImage *image = [[SDImageCache sharedImageCache] queryDiskCacheForKey:[url absoluteString]];
if(!image) {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setTimeoutInterval: 30.0]; // Will timeout after 30 seconds
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil && error == nil) {
image = [UIImage imageWithData:data];
NSData *pngData = UIImagePNGRepresentation(image);
[[SDImageCache sharedImageCache] storeImage:image imageData:pngData forKey:[url absoluteString] toDisk:YES];
}
else {
// There was an error, alert the user
NSLog(#"%s Error: %#", __func__, error);
}
}];
}
}

How does Soundcloud app skip to next track so fast?

I'm building my first app and it's a Soundcloud client. Right now, everytime a track is selected to play, I have to go on and do a NSURLSessionDataTask fetch to get the binary data, but this takes a long time. And when I skip, to the next track, I have to add the logic in to download the next song ahead of time when the current song is playing...though, this is still slow if the user skips quickly:/
The Soundcloud native app skips instantly. How do it work? I've tried using Soundcloud iOS SDK but it is now deprecated.
Here is my song fetch:
-(void)fetchTrack: (SCTrack*)selectedTrack completionHandler: (void(^)(NSData *trackData, NSString *error)) completionHandler; {
NSString* clientID = #"41a5278fd8c704c3eb5a4a0ca38f9036";
NSString* streamURL = selectedTrack.stream_url;
NSString* urlString = [NSString stringWithFormat:#"%#?client_id=%#", streamURL, clientID];
NSURL* url = [[NSURL alloc]initWithString:urlString];
NSLog(#"%#", urlString);
NSMutableURLRequest* request = [[NSMutableURLRequest alloc]initWithURL:url];
request.HTTPMethod = #"GET";
NSURLSessionDataTask* dataTask = [[self session] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse* callResponse = (NSHTTPURLResponse*)response;
if ([callResponse isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger responseCode = [callResponse statusCode];
if (responseCode >= 200 && responseCode <= 299) {
NSData* trackData = data;
NSLog(#"STREAM 200");
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSLog(#"%#", data);
completionHandler(trackData, nil);
}];
}else{
NSLog(#"%ld", (long)responseCode);
}
}
}];
[dataTask resume];
}
This is how I'm playing and attempting to fetch the next song while the current track is playing:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedTrack = self.SCTrackList[indexPath.row];
self.selectedTrackRow = indexPath.row;
[[SoundCloudAPI sharedInstance]fetchTrack:self.selectedTrack completionHandler:^(NSData *trackData, NSString *error) {
self.player = [[AVAudioPlayer alloc]initWithData: trackData error:nil];
[self.player prepareToPlay];
[self.player play];;
}];
[self prepareForNextTrack:self.selectedTrack];
}
-(void)prepareForNextTrack: (SCTrack*)trackPlaying {
self.selectedTrackRow += 1;
self.selectedTrack = self.SCTrackList[self.selectedTrackRow];
[[SoundCloudAPI sharedInstance]fetchTrack:self.selectedTrack completionHandler:^(NSData *trackData, NSString *error) {
self.trackDataToPlay = trackData;
}];
}
- (IBAction)nextPressed:(id)sender {
self.player = [[AVAudioPlayer alloc]initWithData: self.trackDataToPlay error:nil];
[self.player prepareToPlay];
[self.player play];;
[self prepareForNextTrack:self.selectedTrack];
}
Also, I'm new, so I'm sure my code is pretty clunky and would appreciated if anyone can point out ways to improve.
Thanks for pointing in the right direction!
So it turns I made things wayyyy more complicated than had to be. All I had to do was the dataWithContentsOfURL method to go retrieve the song passing in the token.
NSData *data =[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#?oauth_token=%#",self.selectedTrack.stream_url, self.token]]];
And of course, put the data in the AVAudioPlayer.
Basically, getting rid of my fetchTrack function above.

UITableView cell shows same images on fast scrolling

I am creating the iPhone app which shows the app icons & app names in table view.
First time i download the images in user document directory & then make entry in dictionary [value - image stored document directory path & key is image json URL], for showing image in cell first i checked the image is already download or not.
If downloaded, then show the local image which stored on document directory and if not download it.
If i Scrolled normally, cell shows the rights images & if i scrolled it fast, cell shows the same images instead of different.
// code for displaying images
-(void)refreshViews
{
self.appLabelName.text = _applicationObject.name;
self.appLabelName.font = [UIFont fontWithName:#"Helvetica-Bold" size:17];
self.detailTextLabel.text = _applicationObject.artistName;
self.detailTextLabel.font = [UIFont fontWithName:#"Helvetica" size:14];
NSString *appIconStoredPath = [appDelgate.saveAppIconURLAndPathInFile valueForKey:_applicationObject.iconURL];
_appIcon.image = [UIImage imageWithContentsOfFile:appIconStoredPath];
if(!_appIcon.image && appDelgate.hasInternetConnection)
{
[self downloadAppIconsInDirectory];
}
}
// code for download image
-(void)downloadAppIconsInDirectory
{
NSURL *downloadURL = [NSURL URLWithString:_applicationObject.iconURL];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:nil delegateQueue:nil];
__weak ApplicationCell* weakSelf = self;
dispatch_async(queue, ^{
downloadTask = [session downloadTaskWithURL:downloadURL completionHandler:^(NSURL *location, NSURLResponse *respone, NSError *error)
{
NSString *iconName = [location lastPathComponent];
NSMutableString *changeIconName = [[NSMutableString alloc] init];
changeIconName = [iconName mutableCopy];
[changeIconName setString:_applicationObject.bundleId];![enter image description here][1]
NSString *appIconDirectory = [[documentsDirectoryForAppIcons absoluteString] stringByAppendingPathComponent:#"appIcons"];
destinationUrlForAppIcons = [[NSURL URLWithString:appIconDirectory] URLByAppendingPathComponent:changeIconName];
NSError *error1;
BOOL status = [appIconFileManager copyItemAtURL:location toURL:destinationUrlForAppIcons error:&error1];
if (status && !error1)
{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf refreshViews];
});
[appDelgate.saveAppIconURLAndPathInFile setValue:destinationUrlForAppIcons.path forKey:_applicationObject.iconURL];
NSString *dictSavedFilePath = [appDelgate.documentDirectoryPath stringByAppendingPathComponent:#"IconURLsAndPaths.plist"];
dispatch_async(queue, ^{
[appDelgate.saveAppIconURLAndPathInFile writeToFile:dictSavedFilePath atomically:YES];
});
}
}];
[downloadTask resume];
});
}
As it shows, there is no error in code. This means you are priority for queues is wrong. Image must be downloaded before scrolling. As you scroll your view slow, image gets enough time to be downloaded. This means you change your code to this and try ;)
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration
defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:nil
delegateQueue:nil];
__weak ApplicationCell* weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
downloadTask = [session downloadTaskWithURL:downloadURL completionHandler:^(NSURL *location,
{
NSString *iconName = [location lastPathComponent];
NSMutableString *changeIconName = [[NSMutableString alloc] init];
changeIconName = [iconName mutableCopy];
[changeIconName setString:_applicationObject.bundleId];![enter image description here]
[1]
NSString *appIconDirectory = [[documentsDirectoryForAppIcons absoluteString]
stringByAppendingPathComponent:#"appIcons"];
destinationUrlForAppIcons = [[NSURL URLWithString:appIconDirectory]
URLByAppendingPathComponent:changeIconName];
NSError *error1;
BOOL status = [appIconFileManager copyItemAtURL:location
toURL:destinationUrlForAppIcons error:&error1];
if (status && !error1)
{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf refreshViews];
});
[appDelgate.saveAppIconURLAndPathInFile
setValue:destinationUrlForAppIcons.path forKey:_applicationObject.iconURL];
NSString *dictSavedFilePath = [appDelgate.documentDirectoryPath
stringByAppendingPathComponent:#"IconURLsAndPaths.plist"];
dispatch_async(queue, ^{
[appDelgate.saveAppIconURLAndPathInFile writeToFile:dictSavedFilePath
atomically:YES];
});
}
}];
[downloadTask resume];
});
}
replace your refreshViews method with this
-(void)refreshViews
{
self.appLabelName.text = _applicationObject.name;
self.appLabelName.font = [UIFont fontWithName:#"Helvetica-Bold" size:17];
self.detailTextLabel.text = _applicationObject.artistName;
self.detailTextLabel.font = [UIFont fontWithName:#"Helvetica" size:14];
_appIcon.image = nil;
NSString *appIconStoredPath = [appDelgate.saveAppIconURLAndPathInFile valueForKey:_applicationObject.iconURL];
_appIcon.image = [UIImage imageWithContentsOfFile:appIconStoredPath];
if(!_appIcon.image && appDelgate.hasInternetConnection)
{
[self downloadAppIconsInDirectory];
}
}
Its load previous image because your tableview reuse the cell so the imageview also reusing which hold the previous image. so you have to do nil this image

objective c - Efficient way to download an image from web and use it to show in image view

in my app lets say there is 2 views ViewA and ViewB
in ViewA there are buttons for user to select option. And if he push one of them i will pull some images from web via web service and download them to the user's machine also i will put their paths to an array.
Then in ViewB i want to get images from that array and show them in image views
this is how i download images
-(void)startDownload
{
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:#"http://xxxx.com/Tulips.jpg"];
[arr addObject:#"http://xxxx.com/Koala.jpg"];
[arr addObject:#"http://xxxx.com/Penguins.jpg"];
for (int i=0; i<[arr count]; i++) //download array have url links
{
NSURL *URL = [NSURL URLWithString:[arr objectAtIndex:i]];
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc]initWithURL:URL];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if([data length] > 0 && [[NSString stringWithFormat:#"%#",error] isEqualToString:#"(null)"])
{
//make your image here from data.
UIImage *imag = [[UIImage alloc] initWithData:[NSData dataWithData:data]];
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [array objectAtIndex:0];
NSString *imgstr=[NSString stringWithFormat:#"%d",i];
NSString *pngfilepath = [NSString stringWithFormat:#"%#sample%#.jpg",docDir,imgstr];
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(imag)];
[data1 writeToFile:pngfilepath atomically:YES];
img = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:pngfilepath]];
NSLog(#"file is written");
}
else if ([data length] == 0 && [[NSString stringWithFormat:#"%#",error] isEqualToString:#"(null)"])
{
NSLog(#"No Data!");
}
else if (![[NSString stringWithFormat:#"%#",error] isEqualToString:#"(null)"]){
NSLog(#"Error = %#", error);
}
}];
}
}
when i run the app i see that file is written log is working so i think downloading the images is successful but i can't show image in imageview
you may think quiz up app on the store for understanding my problem clearly. quiz up first downloading questions' images then use them in another view. that's what i want exactly.
if my download code is correct how can i show them?
This code will allow you to download an image from the web, and does not require that the image be saved in the document directory:
NSMutableArray *arry = [[NSMutableArray alloc] init];
[arry addObject:#"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRr0WK-Q2t4Xxr1b6Kl7-lXdVEIh_Hj3HiDXk--Qg_0UAY0Y96P6w"];
[arry addObject:#"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRr0WK-Q2t4Xxr1b6Kl7-lXdVEIh_Hj3HiDXk--Qg_0UAY0Y96P6w"];
[arry addObject:#"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRr0WK-Q2t4Xxr1b6Kl7-lXdVEIh_Hj3HiDXk--Qg_0UAY0Y96P6w"];
for (int i=0; i<[arry count]; i++) //download array have url links
{
NSString *string=[arry objectAtIndex:i];
NSURL *url=[NSURL URLWithString:string];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
UIImage *imagemain=[UIImage imageWithData:data];
// CGSize size=imagemain.size;
// UIImage *compimage=[appdel resizeImage:imagemain resizeSize:CGSizeMake(45,45)];
//
// Cell.imgProfile.image=compimage;
// // CGSize size1=compimage.size;
imageView.image=imagemain;
}];
}
Are you updating your UIImageView on the main thread, you can't update UI elements from a background thread. Try
dispatch_sync(dispatch_get_main_queue(),
^{
imageView.image = yourImage;
});
You have to use SDWebImage to cache the image. means the url will not be hit again and again.
#import "UIImageView+WebCache.h"
-(void)startDownload
{
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:#"http://xxxx.com/Tulips.jpg"];
[arr addObject:#"http://xxxx.com/Koala.jpg"];
[arr addObject:#"http://xxxx.com/Penguins.jpg"];
for (int i=0; i<[arry count]; i++) //download array have url links
{
NSString *string=[arry objectAtIndex:i];
NSURL *url=[NSURL URLWithString:string];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:url progress:^(NSUInteger receivedSize, long long expectedSize)
{
// progression tracking code
}completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// here you can setup imageView frames and set the image on imageView
imageView.image=image;
}
}];
}
}
}

AFNetworking for Image Downloads, Unresponsive UI

I'm using AFNetworking to pull images from a URL, resize, store to disk and log the path in Core Data, then load to a table view and store . When the code executes it freezes my UI. I'm not sure if it's the download or the manipulation that's causing my troubles.
The code I'm using is below
- (void)getPhoto:(NSInteger)type forManagedObject:(MyManagedObject*)object {
// download the photo
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:object.photoUrl]];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request success:^(UIImage *image) {
// MyManagedObject has a custom setters (setPhoto:,setThumb:) that save the
// images to disk and store the file path in the database
object.photo = image;
object.thumb = [image imageByScalingAndCroppingForSize:CGSizeMake(PhotoBlockCellButtonWidth, PhotoBlockCellButtonHeight)];
NSError *nerror;
if (![[DataStore sharedDataStore].managedObjectContext save:&nerror]) {
NSLog(#"Whoops, couldn't save: %#", [nerror localizedDescription]);
return;
}
// notify the table view to reload the table
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadTableView" object:nil];
}];
[operation start];
}
And here is a sample code relevant to the setter from my managed object
- (NSString*)uniquePath{
// prepare the directory string
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
// acquire a list of all files within the directory and loop creating a unique file name
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *existingFiles = [fileManager contentsOfDirectoryAtPath:documentsDirectory error:nil];
NSString *uniquePath;
do {
CFUUIDRef newUniqueId = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef newUniqueIdString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueId);
uniquePath = [[documentsDirectory stringByAppendingPathComponent:(__bridge NSString *)newUniqueIdString] stringByAppendingPathExtension:#"png"];
CFRelease(newUniqueId);
CFRelease(newUniqueIdString);
} while ([existingFiles containsObject:uniquePath]);
return uniquePath;
}
- (NSString*)saveImage:(UIImage*)image{
NSString *path = [self uniquePath];
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:YES];
return [NSString stringWithFormat:#"file://%#",path];
}
- (void) setPhoto:(UIImage *)image {
self.photoUrl = [self saveImage:image];
}
I would like to push this to a background thread, but I'm not sure what the implications are with AFNetworking, Core Data, and Messaging in terms of thread safety. Any thought?
AFAIK, the way you are executing your request in incorrect:
[operation start];
you should instead add the operation to an NSOperationQueue:
NSOperationQueue* operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];
(you should correctly memory-manage the queue).
By doing like this, your request will be executed in an async way, it won't block the UI and you will not need to deal with multithreading.
Based on Matt's suggestion, I improved the UI by reworking my call as follows.
- (void)getPhoto:(NSInteger)type forManagedObject:(MyManagedObject*)object {
// download the photo
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:object.photoUrl]];
AFImageRequestOperation *operation = [AFImageRequestOperation
imageRequestOperationWithRequest:request
imageProcessingBlock:^UIImage *(UIImage *image) {
return [image imageByScalingAndCroppingForSize:CGSizeMake(PhotoBlockCellButtonWidth, PhotoBlockCellButtonHeight)];
}
cacheName:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// MyManagedObject has a custom setters (setPhoto:,setThumb:) that save the
// images to disk and store the file path in the database
object.photo = image;
object.thumb = image;
NSError *nerror;
if (![[DataStore sharedDataStore].managedObjectContext save:&nerror]) {
NSLog(#"Whoops, couldn't save: %#", [nerror localizedDescription]);
return;
}
// notify the table view to reload the table
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadTableView" object:nil];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error getting photo");
}];
[operation start];
}

Resources