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);
}
}];
}
}
Related
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.
I am getting image as url from dictionary on ViewController A and I have passed that dictionary to ViewController B.I want that if the user has updated the image then it shows the updated image else it shows the previous image and I am doing the following code for it .Kindly check and tell why is it not working as desired and showing the previous image only in every case.
-(void)showUserImage:(NSURL*)imgUrl
{
[ConnectionManager setSharedCacheForImages];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:imgUrl];
NSURLSession *session = [ConnectionManager prepareSessionForRequest];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse.data) {
UIImage *downloadedImage = [UIImage imageWithData:cachedResponse.data];
dispatch_async(dispatch_get_main_queue(), ^{
_profileImageView.image = downloadedImage;
});
} else {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
if(res.statusCode == 200){
dispatch_async(dispatch_get_main_queue(), ^{
_profileImageView.image = [UIImage imageWithData:data];
});
}
}];
[task resume];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
if(_profileImageView.image == [_detailsDictionary valueForKey:#"ProfilePictureUrl"]) {
NSLog(#"Th url of image is %#",[_detailsDictionary valueForKey:#"ProfilePictureUrl"]);
}
else {
_profileImageView.image = image;
UIImage *updatedImage = _profileImageView.image;
NSData *imageData = UIImageJPEGRepresentation(updatedImage, 100);
NSString *strEncoded = [imageData base64EncodedStringWithOptions:0];
[_detailsDictionary setObject:strEncoded forKey:#"ProfilePictureUrl"];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
#Dirtydanee, He is absolutely correct, you are doing incompatible comparison between Url and UIImage. So please correct this with following code.
NSData *data1 = UIImagePNGRepresentation(previousImage);
NSData *data2 = UIImagePNGRepresentation(currentImage);
if([data1 isEqualToData:data2]) {
//Do something
} else {
//Do something
}
Convert images into NSData and compare the data.
If you want bit-by-bit comparison Please look at the following link:
Generate hash from UIImage
The problem seems to be in this line:
if(_profileImageView.image == [_detailsDictionary valueForKey:#"ProfilePictureUrl"]) {
You are trying to compare the _profileImageView.image, what is UIImage, with [_detailsDictionary valueForKey:#"ProfilePictureUrl"], what is NSURL instance, coming from the dictionary.
What you could do instead, is checking if the picked image and the profileImage is the same.
if(_profileImageView.image == image) {
// etc..
To clear previously cached images, just call:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
Hope this helps!
I am downloading thousands of images asynchronously through AFNetworking an storing them in iDevice but my app goes slow down when errors shows on console "Response time out"
following is the code that I used to download images.
[NSThread detachNewThreadSelector:#selector(DownloadImages) toTarget:self withObject:nil];
-(void)DownloadImages
{
for(int i = 0; i<=4600;i++)
{
NSString *FrameSmall = [NSString stringWithFormat:#"myimageurl%i.png",i];
[self setbuttonImg:FrameSmall];
}
}
-(void)setbuttonImg:(NSString *)str
{
NSArray* badWords = #[#":", #"/", #".",#" "];
NSMutableString* mString = [NSMutableString stringWithString:str];
for (NSString* string in badWords) {
mString = [[mString stringByReplacingOccurrencesOfString:string withString:#""] mutableCopy];
}
NSString *encoded = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:encoded]];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[self saveImage:responseObject withFileName:mString ofType:#"png" inDirectory:documentsDirectoryPath];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[requestOperation start];
}
-(void) saveImage:(UIImage *)image withFileName:(NSString *)imageName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath {
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];
} else {
// ALog(#"Image Save Failed\nExtension: (%#) is not recognized, use (PNG/JPG)", extension);
}
}
All of your images are being downloaded at the same time which isn't a good idea.
You can set the max concurrency with the operationQueue on the AFHTTPRequestOperationManager
http://cocoadocs.org/docsets/AFNetworking/2.0.0/Classes/AFHTTPRequestOperationManager.html#//api/name/operationQueue
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.operationQueue.maxConcurrentOperationCount = 5; //set to max downloads at once.
Best practice would be to only load images that your user is going to see immediately - so the ones in view only. Typically that means just storing the URL, then loading the image when its actually needed. Using just a custom category on UIImageView (AFNetworking provides a similar category) you can load an image into a custom table view cell using:
- (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;
Here's an example with a custom wrapper around that category:
- (void)tableView:(UITableView *)tableView
willDisplayCell:(GameTableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.backgroundColor = [UIColor clearColor];
id game = [self.games objectAtIndex:indexPath.row];
if([game isKindOfClass:[Game class]])
{
Game *aGame = (Game *)game;
cell.titleLabel.text = aGame.gameName;
cell.descriptionLabel.text = aGame.gameDescription;
cell.playGameButton.layer.cornerRadius = 8.0F;
[cell.imageView loadImageFromRemoteURL:aGame.imageURL
withPlaceholder:[UIImage imageFromAssetsNamed:#"game_icon"]
completionHandler:^(UIImage *fetchedImage, NSError *error)
{
if(nil == error)
{
aGame.image = fetchedImage;
// Note: Need to set the image in an imageView somewhere on the main thread.
}
}];
}
}
This means that only game cells on screen will have their images loaded rather than loading them all at once.
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;
}
}];
}
}
}
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];
}