I need to download files from internet inside the app using internal web browser and save them to some custom path.
I think I should use UIWebViewDelegate and intercept lick clicks in
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType;
But here's my question: how can I know when the file is being downloaded (not just other webpage being opened).
I thought I could parse the link and determine if the extension is png, doc or something. But the problem is I need to be able to download the files of any type.
Thank you for any help.
UPDATE:
How can I recognize that this is link is actually for downloading. E.g. in this case -> etextlib.ru/Book/DownLoadPDFFile/19036 <- the link does not have recognizable extension.
You only have two ways to go about this in my opinion, and both involve implementing shouldStartLoadWithRequest:
Define which file types you want to download and check for them in the tapped link.
On any tap the user makes on a link, do the following
If the link is to a known file type, download it.
If the link is a web page (htm, html, asp, ...), just open it
Any other case: Ask user whether he wants to download the link as a file.
AFNetworking
Creating a Download Task
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectorie
sInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
just check [request.URL.absoluteString pathExtension] to see the type of link and do what you want
Related
I just read about NSURLSession and I used it to download images from a url.Here i.e. the method that I wrote :
-(void)startDownloadingWithURLString:(NSString*)urlString andDisplayOn:(UIImageView*)imageView{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:configuration];
NSLog(#"%#",dataResponse);
NSURLSessionDownloadTask *task = [urlSession downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
});
}];
[task resume];
}
Now the problem is that I am calling this method inside "cellForItemAtIndexPath:".So overtime I scroll,this method gets called again and again for each cell(reusability concept).Now,as the download process is asynchronous,I can't know when the downloading is complete.
So what should I do to avoid this problem?I just want to download images once.
There are third party libraries available to solve your purpose. I prefer using SDWebImage for the same. It worked like a charm for me.
Just import
#import<SDWebImage/UIImageView+WebCache.h>
and you can use the method to load the web image
sd_setImageWithURL:
It does the same work for you without hustle.
Refer this link https://github.com/rs/SDWebImage
Hope it helps. Hapy Coding..
I am trying to download a file using AFNetworking (2.5.4). The download completes, the completion handler is called, with error set to nil, everything seeming fine, but the destination file does not exist:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSString *fullPath = [valid path from my apps local manager]
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:req progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
return [NSURL URLWithString:fullPath];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"Saved file to %#.", filePath);
*** [[NSFileManager defaultManager] fileExistsAtPath:filePath.absoluteString] returns NO here ***
}];
[cell.progressView setProgressWithDownloadProgressOfTask:downloadTask animated:YES];
[downloadTask resume];
The file path is a regular path that my app has write access to:
/var/mobile/Containers/Data/Application/APP-GUID-REDACTED/Documents/FILE-NAME-REDACTED.docx
I was using a different method before AFNetworking, and it could write to the exact same path just fine. HTTP response headers show everything perfectly (status 200, correct content length etc.) and if I curl the download URL it downloads the file with no issues. There's no problem with the file.
Why is my destination file not written in completion handler despite no errors?
UPDATE: I've also tried AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; but it changes nothing. I've also tried creating an NSProgress pointer and sending that for the progress argument, but no avail.
Use [NSURL fileURLWithPath:] (not URLWithString).
return [NSURL fileURLWithPath:fullPath];
The problem here is wrong file path or invalid file path. I had same problem here.
Create path like given below :
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
NSURL *filePathURL = [documentsDirectoryURL URLByAppendingPathComponent:[NSString stringWithFormat:#"your file name here",i]];
Now use above path :
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:req progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
return filePathURL;
}
While working with the AFNetworking library I am running into an issue where after downloading JSON data into a file using the AFURLSessionManager downloadTaskWithRequest's destination param code block asynchronously, I am wanting to perform the remaining operations asynchronously as well in its completionHandler block. The problem is the completionHandler block does not seem to run asynchronously.
Would there be a need to setup a new session manager and/or download task to accomplish this. Is there perhaps a better way to do this so the operations can be performed away from the main thread in the completionHandler block.
The reason for wanting to accomplish this is to avoid tying up the main thread in case there's a huge amount of data which needs to be assigned to the self.googleResults array or rather in a for loop using a custom class containing properties for specific key data which would eventually be added as elements to an array.
Here's the code so far...
- (void)viewDidLoad
{
[super viewDidLoad];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURL *url = [NSURL URLWithString:#"https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
// NOTE: This code block runs asynchronously
NSURL *docPathURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [docPathURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
// NOTE: This code block does not run asynchronously
// Would there be a need to create a new session and/or download task here to get the data from the filePath asynchronously?
// Or is there another way to this for the following code?
NSError *jsonSerializationErr;
NSData *jsonData = [NSData dataWithContentsOfURL:filePath];
NSDictionary *reponseDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&jsonSerializationErr];
// self.googleResults is an instance of (NSArray *)
self.googleResults = [[reponseDictionary objectForKey:#"responseData"] objectForKey:#"results"];
NSLog(#"%#", self.googleResults);
}];
[downloadTask resume];
}
When in the middle of download connection is lost or there was no connection initially, completionHandler is fired with error and I have no chance to resume after connection restored. What is the proper way to handle resumable downloading with AFNetworking/reachability? Do I have to create another task because this one is already expired due to network failure or there is a way to revive it?
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *man = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://my_server.com/video/2.mp4"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [man downloadTaskWithRequest:request progress:nil 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) {
NSLog(#"File downloaded to: %#, error: %#", filePath, error);
}];
[man.reachabilityManager startMonitoring];
[man.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
[downloadTask resume];
break;
case AFNetworkReachabilityStatusNotReachable:
default:
[downloadTask suspend];
break;
}
}];
What is the proper way to handle resumable downloading with AFNetworking/reachability? Do I have to create another task because this one is already expired due to network failure or there is a way to revive it?
What you are asking for is how to resume download after a connection failure. You start an NSURLSessionDownloadTask, and during the download the connection fails with an appropriate NSError describing the failure. You want to retry the connection, reusing any previously downloaded data, when the network interface reachability changes.
NSURLSessionDownloadTask can reuse the previously downloaded data if your application grabs the resume data and later passes it to the task that retries the connection. This is documented here and here.
In the case of a network failure, tthe NSError returned will have a userInfo dictionary populated with the NSURLSessionDownloadTaskResumeData key. This will have the data you need to hold on to. When your application attempts to download the data again, the download task should be created using either downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler:, passing in the resume data.
In your case, after a network failure that returns an NSError with the NSURLErrorDomain, an appropriate error code, and a userInfo dictionary with a populated NSURLSessionDownloadTaskResumeData key, you would hold on to the NSData value for the NSURLSessionDownloadTaskResumeData key and start monitoring for reachability changes. If the network interface comes back, in the reachability change notification handler you would create a new download task using downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler: and pass the NSData you retreived from the NSError's userInfo dictionary.
Take a look to this answer.
AFNetworking + Pause/ Resume downloading big files
Another way to do it is setting the HTTPHeaderField:#"Range" from your NSMutableURLRequest. to set this header use a formatted string that looks like: [NSString stringWithFormat:#"bythes=%lld-", downloadedBytes]
But as you are using AFNetworking instead of NSMutableURLRequest then just follow the instructions in the link I posted.
Worth to mention that if you use NSMutableURLRequest you will have to go to the place where you are writing the file and check the size of it in order to set the header and the server can provide you the remaining of the file from the last downloaded byte.
I'm trying to set up a NSInputStream, but my input stream is comes out as nil when I step into the code. The url comes from a Dropbox account.
Getting the file through NSData after I have the url through Dropbox Chooser crashes my iPhone 4 (although not when it is running through XCode). The files are just too big, so I wanted to try NSInputStream.
I saw from I cannot initialize a NSInputStream that the url is supposed to be local. Any idea how to stream a file from Dropbox?
Thanks.
- (void)setUpStreamForFile {
// iStream is NSInputStream instance variable already declared
iStream = [NSInputStream inputStreamWithURL:url];
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
}
Hey don't hesitate to use AFNetworking it is a good framework to manipulate your connections and download content. This is an example to download a file from an URL:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil 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) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
For more information you can check the official information HERE
so thanks to rmaddy's suggestion, I looked up NSURLConnection but decided to use the features of NSURLSession instead.
I used the NSURLSessionDownloadTask like this. Familiarity with the Dropbox chooser should help.
-(IBAction)didPressChooser:(id)sender {
{
[[DBChooser defaultChooser] openChooserForLinkType:DBChooserLinkTypeDirect
fromViewController:self completion:^(NSArray *results)
{
if ([results count]) {
DBChooserResult *_result = results[0];
NSString *extension = [_result.name pathExtension];
if ([extension isEqualToString:#"m4a"]) {
url = _result.link; //url has already been declared elsewhere
DBFileName = _result.name; //DPFileName has also been declared. It's a string
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDownloadTask *getFile = [session downloadTaskWithURL:url];
[getFile resume];
}
} else {
// User canceled the action
}
}];
}
}
Once you have that, you put in another method that works as the completion handler.
-(void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); //I put the file in a temporary folder here so it doesn't take up too much room.
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory, DBFileName];
NSData *data = [NSData dataWithContentsOfURL:location];
[data writeToFile:filePath atomically:YES];
url = [NSURL fileURLWithPath:filePath]; //Yep, I needed to re-assign url for use elsewhere.
//do other stuff with your local file now that you have its url!
}
A bonus is that you get to keep track of the download progress with this awesome feature:
-(void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(#"%f / %f", (double)totalBytesWritten,
(double)totalBytesExpectedToWrite);
}
Anyway, hope someone finds this useful. Works much faster on my iPhone 4 than NSURLSessionDataTask which works in a similar manner.