I m using ASIHTTP Request sample source code for downloading urls.
My question is that how to pause and resume the downloading files.
Please Help.
Deprecation Notice
ASIHTTP was deprecated around 2011, if you want network connectivity now, you should look up AFNetworking for ObjC or Alamofire for Swift, or stick to the native support:
For native support, in iOS 7 Apple added the NSURLSession, which can start download tasks (NSURLSessionDownloadTask), and these can be cancelled and restarted, check the method downloadTaskWithResumeData(_:) from NSURLSession, that has a very good explanation.
ASIHTTP is no longer needed, if you gotta are keeping a legacy app running, well, good luck.
Original Answer
Posted on 2011
ASI itself can resume a download from a file using [myASIRequest setAllowResumeForFileDownloads:YES];, check out the How-to-Use for an example.
Edit: Ok, there's no easy way to pause a download (a [myRequest pause] would be ideal). After reading and trying for a while, turns out that cancelling the request and resending it again is the only way.
For example, here's an example class I made:
#interface TestViewController : UIViewController <ASIHTTPRequestDelegate>
{
ASIHTTPRequest *requestImage;
UIImageView *imageViewDownload;
}
#property (nonatomic, retain) IBOutlet UIImageView *imageViewDownload;
- (IBAction)didPressPauseButton:(id)sender;
- (IBAction)didPressResumeButton:(id)sender;
To start (or restart) the download:
- (IBAction)didPressResumeButton:(id)sender
{
if (requestImage)
return;
NSLog(#"Resumed");
// Request
requestImage = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:#"http://www.myheadhealth.com/_IMAGES/cheese.jpg"]];
// Using a temporary destination path just for show.
// Better pick a suitable path for your download.
NSString *destinationDownloadPath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"cheese.jpg"];
NSString *temporaryDownloadPath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"cheese.jpg-part"];
requestImage.downloadDestinationPath = destinationDownloadPath;
requestImage.temporaryFileDownloadPath = temporaryDownloadPath;
requestImage.allowResumeForFileDownloads = YES;
requestImage.delegate = self;
[requestImage startAsynchronous];
}
In the exmaple above, I'm loading a random image of cheese I found, and storing a temporary download, and a full download.
Canceling the request will keep the contents of the temporary file intact, in fact, I made an UIImageView, and when pressing "Cancel" I could see a part of the downloaded file. If you want to try it out:
- (void)didPressPauseButton:(id)sender
{
if (requestImage)
{
// An imageView I made to check out the contents of the temporary file.
imageViewDownload.image = [UIImage imageWithContentsOfFile:requestImage.temporaryFileDownloadPath];
[requestImage clearDelegatesAndCancel];
requestImage = nil;
NSLog(#"Canceled");
}
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
imageViewDownload.image = [UIImage imageWithContentsOfFile:request.downloadDestinationPath];
requestImage = nil;
}
Related
I have a UITableView with custom cells showng text and an image.(image on right side).
At first, it all worked fine, but then I noticed that when scrolling, the whole tableview was lagging. This was because the app probably downloaded the cells image as the cell was entering the screen when re-using them. I added an NSCache to store the downloaded images, and told the cellForRowAtIndexPath to load the imageName if it existed, otherwise, download from the internet again. However, the cache is so short-term storage, that if I exit my app with home-button, and re-enter, then only some of the images remains, and have to download the images again.
I am trying to find out the best possible way to store images more long-term than with cache. I have read some about NSDirectory, and storing in app library, but haven't figured it out yet..
The most logic solution, I believe, would be to do something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
/*stuff*/
NSString *imageName = [dictionary objectForKey:#"Image"];
UIImage *image = [--Cache-directory-- objectForKey:imageName]; //Try to get it from cache
if(image) //If image was received from cache:
{
cell.imageView.Image = image;
}
else //If not in cache:
{
image = [--local-directory-- objectForKey:imageName]; //Check some local directory
if(image) //If image received from directory:
{
cell.imageView.Image = image;
// + Also save it to the cache?
}
else //If image was in neither cache or local directory, get it from the website with given URL
{
image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:imageURL]];
//Then save to cache and to file?
}
}
}
The images are rarily changed or switched out, but not so rarily that I am willing to implement them in the app beforehand so that I must release an update every time an image is added.
This is what seems logical to me. Am I on the right tracks at all? And how do I "call" the local directory? Like, if I add images to a NSDirectory-object or something, wouldn't this be reset every time? How do I access the local folder?
You are attacking the problem from the wrong direction...
The problem is that the tableView is lagging.
The reason is because you download your images in the main thread.
Even if you will read your images from the disc u still won't get the best scrolling performance.
So must not block your main thread. To do this u can download the picture asynchronously, or use GCD to download in another thread.
like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
/*stuff*/
// this will block your main thread, bad idea..
//image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:imageURL]];
// instead call it in a separate thread
dispatch_queue_t imgDownloaderQueue = dispatch_queue_create("imageDownloader", NULL);
dispatch_async(imgDownloaderQueue, ^{
// download the image in separate thread
UIImage *image = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:imageURL]] autorelease];
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_sync(main_queue, ^{
// this is called in the main thread after the download has finished, here u update the cell's imageView with the new image
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = image;
});
});
dispatch_release(imgDownloaderQueue);
}
Theres no need to save all those images.
If you are getting your image from a url then just use AsyncImageLoader
Use that link and get the h and m files of ASyncImageView and save them in your project.
Import the h file where ever you are doing this
#import 'AsyncImageView.h'
Then use the following code
[[AsyncImageLoader sharedLoader] cancelLoadingURL:image.imageURL];
image.imageURL=[NSURL URLWithString:#"imageURL"];
I was working on UITableView recently and i had a similar problem. One work around I used was since the image was small (I assume yours is as well) and had unique name, i stored them in the app's local documents folder and used core data to hold reference to the image location. I load the image from that location. Should the image change, i change those images. This may not be the most elegant method though. just a suggestion.
As for accessing the local app directory, i use this
// INSTANCE METHOD - get the path and file name for the saved plist
- (NSString *)getFileLocation:(NSString *)location {
// Get all available file system paths for the user
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// Get the "Documents" directory path - it's the one and only on iPhone OS
NSString *documentsPath = [paths objectAtIndex:0];
// Specify the file name, appending it to documentsPath above
NSString *savedFileName = [documentsPath stringByAppendingPathComponent:location];
NSLog(#"File Location %#", location);
// We're done
return savedFileName;
}
Seems like the best solution might be using Core Data. But different from what #Anachid suggested you could just load them from the internet like you were doing before and put them in to the core data directory. I suggest the internet instead of putting them in your solution because you would have to update to get new pictures out. Then from there you can use them as needed.
Im sorry, i feel kind of dumb, but im having trouble implementing the following code for to download multiple files from the server.I have set up the MultipleDownload.h and MultipleDownload.m files as a new Objective-C class within my app. But not sure how call it from my updateView.m to perfrom the file downloads. As per instructions it says i need to initialize and start downloading with the following lines. I cant figure out where to put that code to start downloading files from urls. Do i have to setup a method within that MultipleDownload.m code and call that method it from another object (updateView.m) to initiate the download? or i do i put those lines into one of the methods in (updateView.m)? I honestly tried both and for some reason i keep getting errors, It says that urls. If i put it in updateView.m it says that self.urls and self.downloads is undeclared identifier. I tried to declare NSMutableArray *urls and MultipleDownload *downloads within my updateView.m and it doesnt seen to work either. Any input would be appreciated.
the MultipleDownload.m and MultipleDownload.h code is located on github:
http://github.com/leonho/iphone-libs/tree/master
To initialize and start the download:
self.urls = [NSMutableArray arrayWithObjects:
#"http://maps.google.com/maps/geo?output=json&q=Lai+Chi+Kok,Hong+Kong",
#"http://maps.google.com/maps/geo?output=json&q=Central,Hong+Kong",
#"http://maps.google.com/maps/geo?output=json&q=Wan+Chai,Hong+Kong",
nil];
self.downloads = [[MultipleDownload alloc] initWithUrls: urls];
self.downloads.delegate = self;
What you do is in updateView.h
create #properties for urls (type NSMutableArray) and downloads (type MultiDownload)
then in updateView.m you add a these functions
//Function to start download
- (void) startDownload
{
self.urls = [NSMutableArray arrayWithObjects:
#"YourURLS",
#"YourURLS",
#"YourURLS",
nil];
self.downloads = [[MultipleDownload alloc] initWithUrls: urls];
self.downloads.delegate = self;
}
//download finished for 1 item
- (void) didFinishDownload:(NSNumber*)idx {
NSLog(#"%d download: %#", [idx intValue], [downloads dataAsStringAtIndex: [idx intValue]]);
}
//download finished for all items
- (void) didFinishAllDownload {
NSLog(#"Finished all download!");
[downloads release];
}
I also suggest that if you have problem understanding self.urls and self.downloads to read some more info about objective c and properties, good luck
long time listener, first time caller.
I have a basic memory issue I don't understand, that I'm sure just about any one of you will see in a second. I'm playing around, trying to learn various ways of using UIWebViews, getting strings from URLs, and so on. Specifically, I'm trying to obtain one URL from another. In other words, I have uploaded an html page to the web, containing a URL. The address for that page is coded into the app, giving me a "hook" into the app - I can change the contents of that page and send the app a new URL any time I want. Make sense?
So...retrieving the URL? No problem. Passing it into a string for later use - no problem. But when I set up a tap gesture recognizer, which should take that string, convert it back to an NSURL, and open it in Safari, I get a runtime crash. An NSLog call tells me that the string in question keeps being assigned to all sorts of random things.
The relevant bits of my code follow. I'm sure some of you will tell me there are much better ways to do what I want - and that's certainly welcome. But I'd also really love to know what I'm doing wrong for this particular implementation, as I'm sure it's a basic misunderstanding that I'd like to correct.
Thanks in advance. (And sorry about the formatting of the code block - haven't quite got the hang of posting on here!)
#import "Messing_With_Web_ViewsViewController.h"
#implementation Messing_With_Web_ViewsViewController
#synthesize tapView;
NSString *finalURL;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *firstString = #"http://www.my_web_address.html";
//Of course, I have the correct address here.
NSURL *firstUrl = [NSURL URLWithString:firstString];
NSError * error;
finalURL = [NSString stringWithContentsOfURL:firstUrl
encoding:NSASCIIStringEncoding error:&error];
if ( finalURL )
{
NSLog(#"Text=%#", finalURL);
//everything fine up to here; console prints the correct
contents of "my web address"
}
else
{
NSLog(#"Error = %#", error);
}
//Taps
UITapGestureRecognizer *tapRecognizer;
tapRecognizer=[[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(foundTap:)];
tapRecognizer.numberOfTapsRequired=1;
tapRecognizer.numberOfTouchesRequired=1;
[tapView addGestureRecognizer:tapRecognizer];
[tapRecognizer release];
}
- (void)foundTap:(UITapGestureRecognizer *)recognizer {
NSLog(#"Trying to load %#", finalURL);
//at this point the app either crashes, or the console shows a random memory object
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: finalURL]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
}
- (void)dealloc {
[finalURL release];
[super dealloc];
}
#end
finalURL = [NSString stringWithContentsOfURL:firstUrl
encoding:NSASCIIStringEncoding error:&error];
The line above creates an instance of NSString which you do not own (because you did not call a method whose name includes 'new', 'alloc' 'retain', or 'copy' on it). That finalURL with therefore eventually be destroyed when it is no longer needed. By the time your -foundTap: method runs finalURL has been deallocated and you are just referencing the memory location where it used to be and which now may contain some other object or random data.
Read the memory management guidelines again and also learn to run the static analyzer which should point out mistakes like this.
I've built a basic Web XML to Core Data parsing system, but I am confused about how to set off several parsers at once, and know when they are all done.
This is my current setup, which gets just one parsed xml file ("news"). But I have several xml files I need to parse ("sport", "shop" etc). How would set all of these off, and know when they are all done?
// ViewController.m
DataGrabber *dataGrabber = [[DataGrabber alloc] init];
dataGrabber.delegate = self;
[dataGrabber getData:#"news"];
// DataGrabber delegate method (within ViewController) which gets called when dataGrabber has got all of the XML file
- (void) dataGrabberFinished:(DataGrabber *)dataGrabber
{
NSManagedObjectContext *context = [self managedObjectContext];
NSError *parseError = nil;
// Parser puts the xml into core data. Do I need delegate on this too?
Parser *xmlParse = [[Parser alloc] initWithContext:context];
[xmlParse parseXMLFileWithData:dataGrabber.payload parseError:&parseError];
[xmlParse release];
}
(this a follow-on from this question - Returning data from data-grabbing class from web? )
One option is to count how many you create and then have each one call back to a method that counts down from that total. When its back to zero they are all done.
You will need to set up a delegate for the parser just like you did for the downloader.
i am developing an application where i am loading a urlrequest in the UIWebView and it happened successfully.
But now i am trying to display a UIProgressView when the loading is in progress( starting from 0.0 to 1.0), which is changed dynamically with the progress of loading.
How can i do that?
UIWebView doesn't give you any progress information in the normal mode. What you need to do is first fetch your data asynchronously using an NSURLConnection.
When the NSURLConnection delegate method connection:didReceiveResponse, you're going to take the number you get from expectedContentLength and use that as your max value. Then, inside the delegate method connection:didReceiveData, you're going to use the length property of the NSData instance to tell you how far along you are, so your progress fraction will be length / maxLength , normalized to between 0.0 and 1.0.
Finally, you're going to init the webview with data instead of a URL (in your connection:didFinishLoading delegate method).
Two caveats:
It is possible that the expectedContentLength property of the NSURLResponse is going to be -1 (NSURLReponseUnknownLength constant). In that case, I would suggest throwing up a standard UIActivityIndicator that you shut off inside connection:didFinishLoading.
Make sure that any time you manipulate a visible control from one of the NSURLConnection delegate methods, you do so by calling performSelectorOnMainThread: - otherwise you'll start getting the dreaded EXC_BAD_ACCESS errors.
Using this technique, you can show a progress bar when you know how much data you're supposed to get, and a spinner when you don't know.
You can try to use this subclass of UIWebView which uses the private UIWebView methods - therefore, this solution is not 100% AppStore safe (though some apps do almost 100% use it: Facebook, Google app, ...).
https://github.com/petr-inmite/imtwebview
Using NSURLConnection you are fetching the same data twice,waste of time because it can slow down the user interaction it loads the data twice ,consumes internet data.It is better to make a uiprogress based on timer and when the webview successfuly loaded the webpage it will show the uiprogress loading.in that case you can show a dynamic uiprogress everytime the webpage is loaded.. dont forget to create a uiprogress view and named it myProgressview and set it in file owner's.
HERE IS THE CODE HOPE IT HELPS
#synthesize myProgressView;
- (void)updateProgress:(NSTimer *)sender
{ //if the progress view is = 100% the progress stop
if(myProgressView.progress==1.0)
{
[timer invalidate];
}
else
//if the progress view is< 100% the progress increases
myProgressView.progress+=0.5;
}
- (void)viewDidLoad
{ //this is the code used in order to load the site
[super viewDidLoad];
NSString *urlAddress = #"http://www.playbuzz.org/";
myWebview.delegate = self;
[myWebview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlAddress]]];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{ ///timer for the progress view
timer=[[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(updateProgress:)
userInfo:myProgressView
repeats:YES]retain];
}
- (void)dealloc {
[myProgressView release];
[super dealloc];
}
#end
this code and idea really helps me in solving my problem.