How to track the progress of a download using ASIHTTPRequest (ASYNC) - iphone

I am currently using an asynchronous call to my API (I setup) on my site. I am using ASIHTTPRequest's setDownloadProgressDelegate with a UIProgressView. However I don't know how I can call a selector (updateProgress) which will set a CGFloat 'progress' to the progressView's progress. I tried the following, but both the progresses were zero. Please can you tell me how I can get this working?
(in some method)
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[url stringByAppendingFormat:#"confidential"]]];
[request setDownloadProgressDelegate:progressView];
[NSTimer scheduledTimerWithTimeInterval:1.0f/60.0f target:self selector:#selector(updateProgress:) userInfo:nil repeats:YES];
[request setCompletionBlock:^{~100 lines of code}];
[request setFailedBlock:^{~2 lines of code :) }];
[request startAsynchronous];
- (void) updateProgress:(NSTimer *)timer {
if (progressView.progress < 1.0) {
currentProgress = progressView.progress;
NSLog(#"currProg: %f --- progressViewProg: %f", currentProgress, progressView.progress);
}
else {
[timer invalidate];
}
return;
}

For people still finding this answer: Please note ASI is highly deprecated, you should use NSURLSession or ASIHTTPRequest instead.
One way to achieve what you want would be to set the downloadProgressDelegate to be your own class and implement setProgress:. In this implementation, update your progress variable and then call [progressView setProgress:];
Or in code, set up the request's download progress delegate:
[request setDownloadProgressDelegate:self];
and then add the method to your class:
- (void)setProgress:(float)progress
{
currentProgress = progress;
[progressView setProgress:progress];
}

Try to add in to your request:
[request setShowAccurateProgress:YES];
It won't help you to call updateProgress, ASIHTTPRequest will change progress indicator itself.

BTW: NS*Connection appears to be quite a bit faster than ASI* when downloading stuff.
In any case, the example on this page implies that you don't need to manually "copy" the value from the the download object to the progress view object.
In fact, your timer based code is getting the progress from the progress view which should be showing the progress already. There should be no need for a timer in this code at all, if I understand ASIHTTP* correctly.

Related

Can I stop or cancel loop function when using [NSThread sleepForTimeInterval:2.0]; in IOS

I have a loop function and in it called [NSThread sleepForTimeInterval:2.0];. it mean after 2s, loop function is called. I want when pass new view, this loop function is stop and when back, it is called again.
I use this code to call loop function:
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
loop = YES;
delete=NO;
temp = [FileCompletedArray mutableCopy];
NSOperationQueue *queue = [NSOperationQueue new];
operations = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updateArray) object:nil];
[queue addOperation:operations];
[operations release];
}
And loop function:
-(void)updateArray{
while (loop)
{
NSLog(#"start loop");
if(loop){
[NSThread sleepForTimeInterval:2.0];
NSLog(#"start send request");
NSURL *url1 = [NSURL URLWithString:#"http://server.com"];
NSMutableURLRequest *afRequest = [httpClient requestWithMethod:#"POST" path:nil parameters:params1] ;
operation= [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
NSLog(#" request sent");
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}
];
[httpClient enqueueHTTPRequestOperation:operation];
}
else
return;
}
}
And viewdisappear()
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
loop = NO;
delete=NO;
[operations cancel] ;
}
My problem is when pass new view, updateArray still call. It not stop. Do you have suggestion?
You can try it using key value observers. You can implement the changes in following method which will be automatically called as a particular value changes. First you have to set the notification for the ivar that is going to be changed.
[self addObserver:self forKeyPath:#"loop" options:NSKeyValueObservingOptionNew context:nil];
Then you have to implement the changes as per requirement in the following method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
There are a couple of issues that leap out at me:
If your intent is to actually cancel the sleepForTimeInterval, I don't believe you can do that. There are other mechanisms (such as timers) that are much better suited for that problem.
As an aside, you are issuing an asynchronous request every two seconds. But you have no assurances that your previous request will have completed in that period of time, though. As a result, you can end up with a backlog of multiple network requests that will continue to run after the view is dismissed.
I would have thought that you'd want to initiate the "wait two seconds" inside the completion block of your asynchronous request, to ensure your requests don't get backlogged behind your "every two seconds" logic. Clearly that won't work with your current while loop unless you make the request synchronous, so you might refactor this code, replacing the while loop with something that performs a single request, and in the completion block, waits two seconds before initiating the next request.
You are checking the state of loop, waiting two seconds, and then issuing your request. So if the view disappeared while it was performing the two second sleep, there's nothing here stopping the request from being issued after you finished sleeping.
At the very least, if you're going to use sleepForTimeInterval, you presumably want to check loop state after you finish sleeping. But, to my first point, it's better to use some cancelable mechanism, such as a timer.
If you're saying that your loop never exits, I'd suggest you check to make sure the appearance methods are getting called like you think they should be.
Personally, I'd be inclined to do this with a timer which can easily be canceled/invalidated:
Define my properties:
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, getter = isLooping) BOOL looping;
#property (nonatomic, weak) AFHTTPRequestOperation *operation;
Have my appearance methods set the looping variable and start/stop the scheduled requests as appropriate:
- (void)viewDidAppear:(BOOL)animated
{
self.looping = YES;
[self scheduleRequestIfLooping];
}
- (void)viewWillDisappear:(BOOL)animated
{
self.looping = NO;
[self cancelScheduledRequest];
}
The methods that do the starting and stopping of the scheduled requests would use the NSTimer:
- (void)scheduleRequestIfLooping
{
if ([self isLooping]) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(initiateRequest:) userInfo:nil repeats:NO];
}
}
- (void)cancelScheduledRequest
{
[self.timer invalidate];
self.timer = nil;
// you might want to cancel any `AFHTTPRequestOperation`
// currently in progress, too
[self.operation cancel];
}
Note, whether the cancel method should cancel both the timer and any current request in progress (if any) is up to you.
Finally, put the scheduling of the next request inside the completion block of the current request.
- (void)initiateRequest:(NSTimer *)timer
{
// create AFHTTPRequestOperation
AFHTTPRequestOperation operation = ...
// now schedule next request _after_ this one, by initiating that request in the completion block
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
[self scheduleRequestIfLooping];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
// not sure if you want to schedule request if last one failed
}];
self.operation = operation; // save this in the property
}
In the above, I'm using the main thread for the timer. If you really want to do it on a background queue, you can, but (a) it seems unnecessary to me as the network requests already happen on a background thread; and (b) if you do this on a background thread, you might want to make sure you're doing the necessary thread-safe handling of your state variables and the like. It just seemed like an unnecessary complication to me.

Update views while being touched

I am having an app where I load images in a UITableView. Each row has an image view.
I download images from URL and assign it to UIImageView. The problem here is that the image views are not updating if I was dragging the table view. It only updates when I release the finger.
Another problem is that I have a label in all cells that shows a counter which is incremented using a timer. Timer action is also not getting called when my finger is on the table view.
How do I do the updates in both these cases?
May be this is a very basic question. But I have no clue.
Thanks everyone!
EDIT: I have noticed that -connection:didReceiveResponse: method is not getting called when the view is being touched.
EDIT 2: I tried adding run loop with NSURLConnection. My method looks like this.
- (void)start {
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:self.imageURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:self.timeoutInterval];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
NSPort* port = [NSPort port];
NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop
[rl addPort:port forMode:NSRunLoopCommonModes];
[_connection scheduleInRunLoop:rl forMode:NSRunLoopCommonModes];
[_connection start];
[rl run];
}
This loads the images even when I am touching on the view. But the table view is scrolling very slowly. Its not even scrolling. It only moves when I drag. No movement after I release the finger.
Problem 1
The solution is to use downloading mechanism that doesn't get blocked while you drag the table view. SDWebImage is an asynchronous image downloader with cache support with an UIImageView category. This will let your image view to be updated even if you are dragging your table. I have faced a the same issue in my project where i used SDWebImage. Just a call like this will do.
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Problem 2
If its scheduledTimer will not get called while the main thread is tracking touches. Do something like this. Just create a timer and add to loop
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
this will not interrupt your timer.
this might be some helpful to you because you downloading images for displaying in UItableViewCells here i am giving you the example of the anything in background without blocking UI there you can get good tutorial link.they have mentioned enough good way same thing.go through this link
And Rest of the thing could be solved with the help of as Meera j Pai mentioned in his Answer.
I solved the problem with both NSConnection & NSTimer using the answers given by #Amar & #Meera J Pai, by using NSRunLoop.
I have changed my connection code to be like this.
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]
[_connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[_connection start];
It worked great!
Thanks everyone.

How to check how much data send to server with three20?

I want make some progress bar in my application with three20 library when sending data to server. Can someone help me with this?
I am using
- (void)requestDidStartLoad:(TTURLRequest*)request
and
- (void)requestDidFinishLoad:(TTURLRequest*)request
You can implement
- (void)requestDidUploadData:(TTURLRequest *)request
and check the totalBytesLoaded and totalBytesExpected properties, per http://api.three20.info/protocol_t_t_u_r_l_request_delegate-p.php#a9c831806650a4b887f5cf963f6bbba1c.
I have found solution.
You must call
[request send];
to call this delegate function:
- (void)requestDidUploadData:(TTURLRequest *)request
{
float progress = request.totalBytesLoaded / (float)request.totalBytesExpected;
HUD.progress = progress;
HUD.labelText = [NSString stringWithFormat:#"Progress %d%%", (int)(progress*100)];
}
if you call sendSynchronously data will send but you can't get totalBytesLoaded.

NSURLConnection Async Request and display a "Loading" alert view or subview while fulfilling request

I'm trying to figure out the optimal way to perform an NSURLConnection Async Request and have the UI shielded with an alert view while the request is being fulfilled. I've had a lot of trouble getting this to work with a synchronous request because I could not figure out how to use the multi threading features or operation queues effectively with a synchronous request, so I figure this is the best way to go. Pseudo code or actual code is fine, I just need to know which direction to go. So far I figure:
Create a UIAlertView property
Create a void function that initiates the NSURLConnection, and display the view right after it initiates
Use the delegate method to close the AlertView window -(void)connectionDidFinishLoading or something like that.
Is it this simple, or am I missing something?
Probably the easiest way to do it is to use the UIApplication.networkActivityIndicatorVisible property, and do a sync request in a background thread.
-(void)loadURLInBackground:(NSURL*)url {
NSURLRequest* req = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];
NSURLResponse* response = nil;
NSError* err = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData* data = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&err];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if( data != nil ) {
[self performSelectorOnMainThread:#selector(processData:) withObject:data waitUntilDone:NO];
} else {
[self performSelectorOnMainThread:#selector(processError:) withObject:err waitUntilDone:NO];
}
}
Use [self performSelectorInBackground:#selector(loadURLInBackground:) withObject:url]; to call the method, then just implement processData: and processError:.
You do not want to use a UIAlertView - that is a modal dialog.
You want to use something like UIActivityIndicatorView to show the spinner while the background activity is going on.
Then, as you say, your delegate method can stopAnimating the activity indicator view.
If you want to show a message like "Downloading ...", then you can wrap your activity indicator inside another view, display that view, and remove it when the delegate calls back.

How can I SIMPLY perform two tasks at once in my iPhone app? (threading?)

The Situation:
Somewhere in my app I start downloading data from my server. Before downloading starts, I would like to update a UILabel to say #"Now Downloading...". And set it back to blank when downloading is over.
The Problem: It seems like the download takes up all of the computers attention, and the UILabel never gets updated until the very end (at which downloading is already over) and so is set back to blank (or, never-visible in real time).
Question:
How can I SIMPLY update my UILabel to say "Now Downloading" just before the download?
label.text = #"Downloading";
NSOperationQueue *operationQueue = [[NSOperationQueue]alloc]init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(download) object:#"http://www.google.com"];
[operationQueue addOperation:operation];
[operation release];
- (void)download:(NSString *)url
{
// do the download
[self performSelectorOnMainThread:#selector(didFinishDownload) withObject:nil waitUntilDone:NO];
}
- (void)didFinishDownload
{
label.text = #"";
}
If you use NSURLRequest -> NSURLConnection and the NSURLConnection's delegate methods this will perform the download in the background and will notify the delegate of incoming data. This will also allow you to display a progress.