In my iphone app I am downloading some number of images from the web. It doesn't matter if it blocks the UI thread, in fact it needs to block UI thread till fully downloaded. Once done, I notify the UI to wake up and display them.
My (simplified) code goes like this:
for (int i=0; i<10; i++)
{
//call saveImageFromURL (params)
}
//Call to Notify UI to wake up and show the images
+(void) saveImageFromURL:(NSString *)fileURL :(NSString *)destPath :(NSString *)fileName
{
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
NSFileManager * fileManager = [NSFileManager defaultManager];
BOOL bExists, isDir;
bExists = [fileManager fileExistsAtPath:destPath isDirectory:&isDir];
if (!bExists)
{
NSError *error = nil;
[fileManager createDirectoryAtPath:destPath withIntermediateDirectories:YES attributes:nil error:&error];
if (error)
{
NSLog(#"%#",[error description]);
return;
}
}
NSString *filePath = [destPath stringByAppendingPathComponent:fileName];
[data writeToFile:filePath options:NSAtomicWrite error:nil];
}
When I am done with my for loop, I am pretty sure that all images are stored locally. And it works fine in simulator.
However it does not work well on my device. UI wakes up before images are stored. And almost all images seem empty.
What am I doing wrong?
Check that if your device can download those images, visit the image URLs in Mobile Safari to test. dataWithContentsOfURL: will return nil OR it's not a correct image data, like 404 not found
Log errors of [data writeToFile:filePath] to see the details of saving .
After some research, I used AFHttpClient enqueueBatchOfHTTPRequestOperations to accomplish multiple file downloads.
Here is how it goes:
//Consider I get destFilesArray filled with Dicts already with URLs and local paths
NSMutableArray * opArray = [NSMutableArray array];
AFHTTPClient *httpClient = nil;
for (id item in destFilesArray)
{
NSDictionary * fileDetailDict = (NSDictionary *)item;
NSString * url = [fileDetailDict objectForKey:#"fileURL"];
if (!httpClient)
httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:url]];
NSString * filePath = [photoDetailDict objectForKey:#"filePath"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[opArray addObject:operation];
}
[httpClient enqueueBatchOfHTTPRequestOperations:opArray progressBlock:nil completionBlock:^(NSArray *operations)
{
//gets called JUST ONCE when all operations complete with success or failure
for (AFJSONRequestOperation *operation in operations)
{
if (operation.response.statusCode != 200)
{
NSLog(#"operation: %#", operation.request.URL);
}
}
}];
Related
I am downloading cards from server using asynchronous request and I want the moment I finished downloading, I should be navigated to some other screen. I am downloading cards using this set of code.
NSMutableArray *array=[[NSMutableArray alloc]initWithObjects:[dictTemp objectForKey:#"image_1_url"],[dictTemp objectForKey:#"image_2_url"],[dictTemp objectForKey:#"image_3_url"], nil];
for(int i=0;i< [array count];i++)
{
NSURL* url = [NSURL URLWithString:[array objectAtIndex:i]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
NSString *stringName=[NSString stringWithFormat:#"downloadimage%d",i+1];
UIImage *tempImage=[[UIImage alloc]initWithData:data];
[self saveLocally:tempImage andNAme:stringName];
}
[self performSelectorOnMainThread:#selector(update) withObject:nil waitUntilDone:YES];
}];
But the problem is, I am navigating to some other screen in update method, but it gets called before completion of asynchronous request.
I am downloading three images one by one using for loop as specified in the code, and I want to call update method after downloading all the three cards.
Thanks in advance
You can do the below if don't want want to change:
NSMutableArray *array=[[NSMutableArray alloc]initWithObjects:[dictTemp objectForKey:#"image_1_url"],[dictTemp objectForKey:#"image_2_url"],[dictTemp objectForKey:#"image_3_url"], nil];
NSInteger reqCounts = [array count];
for(int i=0;i< [array count];i++)
{
NSURL* url = [NSURL URLWithString:[array objectAtIndex:i]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
NSString *stringName=[NSString stringWithFormat:#"downloadimage%d",i+1];
UIImage *tempImage=[[UIImage alloc]initWithData:data];
[self saveLocally:tempImage andNAme:stringName];
reqCounts --;
}
if (reqCounts == 0) {
[self performSelectorOnMainThread:#selector(update) withObject:nil waitUntilDone:YES];
}
}];
Better to check this awesome answer.
Instead of using the NSURLRequest class, the NSSession Object is recommended in iOS7. Try rewriting your code this way.
.......
NSURL* url = [NSURL URLWithString:[array objectAtIndex:i]];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSData *data = [[NSData alloc] initWithContentsOfURL:location];
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
..........
}
dispatch_async(dispatch_get_main_queue(), ^{
//call your update method here
});
Hope it helps.
Trying to write a method for my iPhone program that given a URL address to a file, it would download to the iOS App's Documents Directory.
Following AFNetowrking's Documentation, it seems to work fine except that the filename is always some garbage.
I'm using Xcode 5 with AFNetworking 2.0 added to my project. Here's the code that I have so far:
//#import "AFURLSessionManager.h"
//On load (or wherever):
[self downloadFile:#"http://www.irs.gov/pub/irs-pdf/fw4.pdf"];
-(void)downloadFile:(NSString *)UrlAddress
{ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:UrlAddress];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[targetPath lastPathComponent]];
}
completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
}
The end result is that the file is successfully downloaded to my documents directory, but with a garbled name:
File downloaded to: file:///Users/myname/Library/Application%20Support/iPhone%20Simulator/7.0/Applications/25188DCA-4277-488E-B08A-4BEC83E59194/Documents/CFNetworkDownload_60NWIf.tmp
The end result I'm expecting:
File downloaded to: file:///Users/myname/Library/Application%20Support/iPhone%20Simulator/7.0/Applications/25188DCA-4277-488E-B08A-4BEC83E59194/Documents/fw4.pdf
I used cocoapods to add AFNetworking to my project:
pod 'AFNetworking', "~> 2.0"
Lastly, what do I need to do to get the progress of the download?
This is the answer I was able to create:
-(void)downloadFile:(NSString *)UrlAddress
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:UrlAddress]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *pdfName = #"The_PDF_Name_I_Want.pdf";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:pdfName];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
NSLog(#"Download = %f", (float)totalBytesRead / totalBytesExpectedToRead);
}];
[operation start];
}
I am open to improvements or suggestions :)
Simply you can replace this line:
return [documentsDirectoryPath URLByAppendingPathComponent:[targetPath lastPathComponent]];
with:
return [documentsDirectoryPath URLByAppendingPathComponent:#"fw4.pdf"];
since [targetPath lastPathComponent] returns something like CFNetworkDownload_60NWIf.tmp
I just started using AFNetworking and so far it worked fine for just getting data from the web.
But now I have a list of files that need to be downloaded to the device
for(NSDictionary *issueDic in data)
{
Issue *issue = [[Issue alloc] init];
...
[self getOrDownloadCover:issue];
...
}
getOrDownloadCover:issue checks if a file already exists locally, if it does, it just saves that path, if it doesn't, it downloads the file from a given url
- (void)getOrDownloadCover:(Issue *)issue
{
NSLog(#"issue.thumb: %#", issue.thumb);
NSString *coverName = [issue.thumb lastPathComponent];
NSLog(#"coverName: %#", coverName);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
__block NSString *filePath = [documentsDirectory stringByAppendingPathComponent:coverName];
if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
// Cover already exists
issue.thumb_location = filePath;
NSLog(#"issue.thumb_location: %#", filePath);
}
else
{
NSLog(#"load thumb");
// Download the cover
NSURL *url = [NSURL URLWithString:issue.thumb];
AFHTTPClient *httpClient = [[[AFHTTPClient alloc] initWithBaseURL:url] autorelease];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:issue.thumb parameters:nil];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
issue.thumb_location = filePath;
NSLog(#"issue.thumb_location: %#", filePath);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];
[operation start];
}
}
getOrDownloadCover:issue can be called 20 times in a row, so I would need to put all my requests into a queue, and once the queue is completed, it should still be able to save the path (or just send out a notification, since I already know what the path is)
Any suggestions for this?
Add an NSOperationQueue to an object you can get the instance of at any point in your application, for example in your appDelegate. Then just add the AFHTTPRequestOperation to this queue like:
[[(AppDelegate *) [UIApplication sharedApplication].delegate operartionQueue] addOperation:operation];
Just handle the saving in the completion block, either call a methode from this block on the main thread or post a NSNotification.
To call the main thread use GCD :
dispatch_async(dispatch_get_main_queue(), ^{
// Call any method from on the instance that created the operation here.
[self updateGUI]; // example
});
Not sure if this is helpful but...
Instead of using your own operationQueue you could use AFHTTPClient's operationQueue. Enqueue single operations with enqueueHTTPOperation or enqueue an array of operations with enqueueBatchOperations (I'm recalling these method names from memory, likely off a little).
As far as storing data specific to each operation, you could subclass AFHTTPRequestOperation and set properties for the path you'd like to store. Something like this.
I have written code like this but I can't understand how to find if the data is present or not in the URL. Can anyone help me in solving this problem?
Note: This code terminates when the loop is entering the second condition. That is where it's terminating.
-(void)getdetails
{
NSLog(#"in get details");
NSURL *jsonurl=[NSURL URLWithString:#"http://www.myappdemo.com/checkout/services/getonlineusers.php"];
NSMutableURLRequest *request=[[[NSMutableURLRequest alloc]init ]autorelease];
[request setURL:jsonurl];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSError *error;
NSURLResponse *response;
NSData *serverReply = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSString *replyString = [[NSString alloc] initWithBytes:[serverReply bytes] length:[serverReply length] encoding: NSASCIIStringEncoding];
if([replyString isEqualToString:#"Invalid."]){ // i have not set the php code to output "invalid" so this will not work for now ...
NSLog(#"%#",replyString);
}
else {
NSMutableArray *tempArray =[replyString JSONValue];
int count=0;
self.temparray=tempArray;
for(int i=0;i<[tempArray count];i++)
{
///////Here in this loop when it is entering it is terminating /////////
NSDictionary *dict=[tempArray objectAtIndex:i];
NSLog(#"DICT is %#",dict);
NSString *string=[dict objectForKey:#"profilepic"];
NSURL *finalURL = [NSURL URLWithString:[string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
NSLog(#"encoding string is %#",finalURL);
NSURL *url=[NSURL URLWithString:string];
NSString *source = [NSString stringWithContentsOfURL:finalURL encoding:NSUTF8StringEncoding error:nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:url];
NSLog(#"url is %#",url);
NSData *data=[NSData dataWithContentsOfURL:url];
if(!(data==nil))
{
NSLog(#"data is there");
UIImage *image=[[UIImage alloc]initWithData:data];
[self.array addObject:image];
[image release];
}
else {
/*
NSLog(#"if data is null condition block");
UIImage *image=[[UIImage alloc]init];
[self.array addObject:image];
[image release];
*/
}
count=count+1;
NSLog(#"count is %d",count);
}
}
[replyString release];
}
Where does the app terminate and what is the exception? Have you stepped through to see what the array object looks like during each iteration? Is it failing at NSData initWithContentsOfURL? Why don't you issue that as a separate synchronous request and test to see if you got a response?
Regarding your first (and any subsequent) synchronous request, it would probably be good to add a check to ensure you received a valid response (sorry for the formatting, the code tag isn't playing nicely at the moment)
if (response!=nil) {if([response isKindOfClass:[NSHTTPURLResponse class]]) {
// you have a valid response }}
I am trying to download the image from the url http://a3.twimg.com/profile_images/414797877/05052008321_bigger.jpg
I am using the following code, but image is not saved in the device. I want to know what I am doing wrong.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://a3.twimg.com/profile_images/414797877/05052008321_bigger.jpg"]];
[NSURLConnection connectionWithRequest:request delegate:self];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localFilePath = [documentsDirectory stringByAppendingPathComponent:#"pkm.jpg"];
NSData *thedata = NULL;
[thedata writeToFile:localFilePath atomically:YES];
UIImage *img = [[UIImage alloc] initWithData:thedata];
I happen to have exactly what you are looking for.
Get Image From URL
-(UIImage *) getImageFromURL:(NSString *)fileURL {
UIImage * result;
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
result = [UIImage imageWithData:data];
return result;
}
Save Image
-(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 {
NSLog(#"Image Save Failed\nExtension: (%#) is not recognized, use (PNG/JPG)", extension);
}
}
Load Image
-(UIImage *) loadImage:(NSString *)fileName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath {
UIImage * result = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/%#.%#", directoryPath, fileName, extension]];
return result;
}
How-To
//Definitions
NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//Get Image From URL
UIImage * imageFromURL = [self getImageFromURL:#"http://www.yourdomain.com/yourImage.png"];
//Save Image to Directory
[self saveImage:imageFromURL withFileName:#"My Image" ofType:#"png" inDirectory:documentsDirectoryPath];
//Load Image From Directory
UIImage * imageFromWeb = [self loadImage:#"My Image" ofType:#"png" inDirectory:documentsDirectoryPath];
If you set theData to nil, what do you expect it to write to the disk?
What you can use is NSData* theData = [NSData dataWithContentsOfURL:yourURLHere]; to load the data from the disk and then save it using writeToFile:atomically:. If you need more control over the loading process or have it in background, look at the documentation of NSURLConnection and the associated guide.
This is the code to download the image from url and save that image in the device and this is the reference link.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://a3.twimg.com/profile_images/414797877/05052008321_bigger.jpg"]];
[NSURLConnection connectionWithRequest:request delegate:self];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localFilePath = [documentsDirectory stringByAppendingPathComponent:#"pkm.jpg"];
NSData *thedata = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://a3.twimg.com/profile_images/414797877/05052008321_bigger.jpg"]];
[thedata writeToFile:localFilePath atomically:YES];
Get Image From URL
-(UIImage *) getImageFromURL:(NSString *)fileURL {
UIImage * result;
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
result = [UIImage imageWithData:data];
return result;
}
This worked great for me but I ran into memory issues with CFData (store). Fixed it with an autoreleasepool:
-(UIImage *) getImageFromURL:(NSString *)fileURL {
#autoreleasepool {
UIImage * result;
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
result = [UIImage imageWithData:data];
return result;
}
}
Since we are on IOS6 now, you no longer need to write images to disk neccessarily.
Since iOS5 you are now able to set "allow external storage" on an coredata binary attribute.
According to apples release notes it means the following:
Small data values like image thumbnails may be efficiently stored in a
database, but laarge photos or other media are best handled directly by
the file system. You can now specify that the value of a managed
object attribute may be stored as an external record - see setAllowsExternalBinaryDataStorage:
When enabled, Core Data heuristically decides on a per-value basis if
it should save the data directly in the database or store a URI to a
separate file which it manages for you. You cannot query based on the
contents of a binary data property if you use this option.
Hi It is clear that you are writing NULL data to your file.
In your code statement NSData *thedata = NULL; indicates that you assign NULL value to your data.
You are writing NULL data to your file as well.
Please check your code once again.
-(IBAction)BtnDwn:(id)sender
{
[self.actvityIndicator startAnimating];
NSURL *URL = [NSURL URLWithString:self.dataaArray];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath];
NSURL *documentURL = [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:[documentURL path]];
if (exists)
{
NSLog(#"not created");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Download"
message:#"sory,file already exists"
delegate:nil
cancelButtonTitle:#"cancel"
otherButtonTitles:nil];
[alert show];
}
else
{
[[NSFileManager defaultManager] moveItemAtURL:location toURL:documentURL error:nil];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Download"
message:#"Succesfully downloaded"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[self.actvityIndicator stopAnimating];
NSLog(#"wait downloading......");
[alert show];
}
}];
[downloadTask resume];
}
Here is how you can save an image asynchronously in Swift:
requestImage("http://www.asdf.com/89asdf.gif") { (image) -> Void in
let myImage = image
}
func requestImage(url: String, success: (UIImage?) -> Void) {
requestURL(url, success: { (data) -> Void in
if let d = data {
success(UIImage(data: d))
}
})
}
func requestURL(url: String, success: (NSData?) -> Void, error: ((NSError) -> Void)? = nil) {
NSURLConnection.sendAsynchronousRequest(
NSURLRequest(URL: NSURL (string: url)!),
queue: NSOperationQueue.mainQueue(),
completionHandler: { response, data, err in
if let e = err {
error?(e)
} else {
success(data)
}
})
}
Its included as a standard function in my repo:
https://github.com/goktugyil/EZSwiftExtensions
Here's an example of how I download banners to my app. I download the images in the background, and most of my apps do not use reference counting so I release objects.
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:#selector(loadImageInBackground) toTarget:self withObject:nil];
}
- (void) loadImageInBackground {
NSURL *url = [[NSURL alloc] initWithString:#"http://yourImagePath.png"];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[url release];
UIImage *result = [[UIImage alloc] initWithData:data];
[data release];
UIImageView *banner_ImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
[self.view addSubview:banner_ImageView];
banner_ImageView.image = result;
[result release];
}