Tapping to cancel a Download - ios

I want to do the following:
1)DownLoad a series of files whose URLs are stored in an NSMutableArray.
2)During the download process a MBProgressHUD shows the download status.
3)At any point of download I want to cancel the download, when the user touches the screen.
-(void)singleTap:(UITapGestureRecognizer*)sender
{
NSLog(#"%#",#"tapped");
self.downLoadHud.detailsLabelText=#"";
self.downLoadHud.labelText=[SAGlobal stringForValue:#"CANCELLINGDOWNLOAD"];
SharedAppDelegatee.downLoadCancelFlag=YES;
}
-(void) startFileDownLoadingWithHUD
{
self.downLoadHud=[MBProgressHUD showHUDAddedTo:[SharedAppDelegatee window] animated:YES];
self.downLoadHud.mode = MBProgressHUDModeIndeterminate;
UITapGestureRecognizer *HUDSingleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(singleTap:)];
[self.downLoadHud addGestureRecognizer:HUDSingleTap];
self.downLoadHud.labelText = #"Initialising..";
self.downLoadHud.detailsLabelText =#"";
[self.downLoadHud setColor:[UIColor blackColor]];
dispatch_queue_t dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(dispatchQueue, ^(void)
{
[self startAllFilesInArrayDownload];
//downloadcancelled or downloadfinished
while (!(SharedAppDelegatee.downLoadCancelFlag)||(SharedAppDelegatee.SAdownloadMode==0)) {
self.downLoadHud.labelText =[NSString stringWithFormat:#"Downloading..[%d/%d]",self.self.downloadErrorCount+self.downloadSuccessCount,[filesToDownLoad count]];
self.downLoadHud.detailsLabelText =[SAGlobal stringForValue:#"TAPTOCANCEL"];
//NSLog(#"DOWNLOADING------+");
}
////////////////////////////////
dispatch_sync(dispatch_get_main_queue(), ^{
[self.downLoadHud hide:YES];
//downLoadHud
});
});
}
The download is done with
for (downDict in filesToDownLoad)
{
//[adm downloadURL:[downDict objectForKey:#"url"] destPath:[downDict objectForKey:#"toFile"]];
NSURL *aUrl = [NSURL URLWithString:[downDict objectForKey:#"url"]];
[self.downloadManager addDownloadWithFilename:[downDict objectForKey:#"toFile"] URL:aUrl];
//[urlStringsArray addObject:[downDict objectForKey:#"url"]];
}
The "downloadManager" is an object of class "DownloadManager" which is obtained
https://github.com/robertmryan/download-manager
I could succesively download all files. I am NOT able to cancel the download in the middle of download. When the user taps the button, it waits a long time, and after some time, the "singleTap" method is called.
The number of files downloaded and failed are CORRECTLY shown. What is wrong with my code?. Can any one suggest me a better example or way to handle "Showing a busy hud + downloading+ tap to cancel feature similar as shown below.

While I am certain that this is a great utility and a lot of work went into it I was immediately concerned when i saw that the repo was two years old.
These types of things are a great help and i can't begin to express my appreciation for the authors and their generosity in sharing so much hard work. Unfortunately, if they are not maintained then they can become difficult for the user to update.
Apple has more recently introduced new functionality with NSURLSession.
This is pretty easy to use and is much more powerful than NSURLConnection.
It specifically includes the ability to pause, resume and cancel network downloads.

Related

iOS - Show SVProgressHUD message on screen without interrupting user interaction

I'm making a synchronize function that syncs local Core Data with the server. I want to make the synchronizations happen in the background without disrupting user interaction. When I receive the response (whether success or failure) the app should display a message somewhere on the screen to notify the user about the outcome.
UIAlertController is not a good choice because it will block user action.
Currently I'm using SVProgressHUD:
__weak StampCollectiblesMainViewController *weakSelf = self;
if ([[AppDelegate sharedAppDelegate] hasInternetConnectionWarnIfNoConnection:YES]) {
[_activityIndicator startAnimating];
[Stamp API_getStampsOnCompletion:^(BOOL success, NSError *error) {
if (error) {
[_activityIndicator stopAnimating];
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeClear];
[SVProgressHUD setAnimationDuration:0.5];
[SVProgressHUD showErrorWithStatus:#"error syncronize with server"];
}
else {
[_activityIndicator stopAnimating];
[featuredImageView setImageWithURL:[NSURL URLWithString:[Stamp featuredStamp].coverImage] usingActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[yearDropDownList setValues:[Stamp yearsDropDownValues]];
[yearDropDownList selectRow:0 animated:NO];
[weakSelf yearDropDownListSelected];
[SVProgressHUD dismiss];
}
}];
}
Is there a modification I can make so the user can still interact with the app? I just want to show the message without taking up too much space. Any help is much appreciated. Thanks.
Looks like the easiest thing will be to use SVProgressHUDMaskTypeNone.
Also check out this issue.
Sorry but you gonna have to build your own custom view.
In fact it's not that difficult. What I would do is simply add a small view on the top of the screen with your custom message and a close button (to allow user to hide quickly the message). This is usually done by adding this new view to the current window, so that it will be on the top of every view and won't block the UI (except the part hidden by that view :) )

Updating a progress indicator with synchronous downloads in iOS, not able to update UI on main thread?

I'm having a problem where I'm unable to update UI when performing synchronous downloads. I would expect that using synchronous APIs would ensure that code executes in order (which it doesn't seem to be doing), which is really confusing me.
The following code is in a UICollectionView's didSelectItemAtIndexPath and is not wrapped in any asynchronous block or anything.
Any ideas on what I can do to be able to update the UI (most importantly a progress indicator) as these tasks occur? I think that the way it is currently laid out should work, but for some reason it's not able to update until the code has all 'executed'.
if ([internetReachable isReachable]) {
//does not become visible until after
self.circleProgress.alpha = 1.0;
//lots of downloading and saving with NSData dataWithContentsOfURL followed by this:
for (int i = 1; i < pages.count; i++) {
NSString *number;
if (i < 10) {
number = [NSString stringWithFormat:#"00%d", i];
}
else if (i < 100) {
number = [NSString stringWithFormat:#"0%d", i];
}
else {
number = [NSString stringWithFormat:#"%d", i];
}
NSURL *imageURL = [NSURL URLWithString:[NSString stringWithFormat:#"http://books.hardbound.co/%#/%#-%#.png", slug, slug, number]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
[df setObject:imageData forKey:[NSString stringWithFormat:#"%#-%#", slug, number]];
CGFloat progress = ((CGFloat)i / pages.count);
//only runs for the last iteration, rather than calling the method to update the progress indicator each iteration and allowing it to update before going back to the next iteration as I would expect
[self updateProgressBarWithAmount:[NSNumber numberWithFloat:progress]];
NSLog(#"progress after: %f", self.circleProgress.progress);
}
}
UI can only be executed on the main thread. Since the main thread is busy doing the downloading, it can't update the UI. It's almost never a good idea to perform any long running operations on the main thread. You should make the download asynchronous, and update the UI on the main thread.
The loop in the code you posted will only be executed after lots of downloading and saving with NSData dataWithContentsOfURL is performed, all the while the application will be unresponsive, and that's very poor UX. Take a look at this question for a much better implementation of a progress bar.
I am not by any means qualified to explain what exactly happens during each render loop and why updateProgress doesn't actually let a screen render occur before you block the main thread again, but I am able to provide a solution.
After you update the progress of the progress view, you want the changes to get rendered "right now". This means you have to tell the current run loop to run one iteration, and then return to you so you can do another long running task.
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
Call that whoever you want the progress view to update, and it will do a screen render and then return to you.
I got this from this answer
However, you really should be doing this asynchronously.
(Apologies for any typos, as this is being typed on my phone)

Navigation taking time

I am navigating by clicking a button to a viewcontroller where I am loading webview,but after clicking the button it is taking some time,how to navigate faster and load webview faster,please help.I have only the following code in second viewcontroller.
-(void)viewWillAppear:(BOOL)animated{
self.navigationController.navigationBarHidden=YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myurl"]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.wb loadRequest:request] ;
});
});
}
Try this code
-(void)viewWillAppear:(BOOL)animated{
self.navigationController.navigationBarHidden=YES;
dispatch_queue_t jsonParsingQueue = dispatch_queue_create("jsonParsingQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(jsonParsingQueue, ^{
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myurl"]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.wb loadRequest:request] ;
});
});
}
If I understand your question, there isn't much you can do to make if faster. That request speed is based on internet speed (Over which you don't have much of a control).
Also the request already happens asynchronously, so there's no need to do that yourself.
You are combining two things as you navigate to your webview
loading and displaying a view
Retrieving data from the internet
You can only directly influence the first one, the second one is well beyond your control.
By performing the asynchronous NSURLRequest from within the viewWillAppear method, you are telling iOS to delay showing the view until the internet has given you all the data it needs.
A better approach is to configure all the visual elements of your new view, display that view in the interface, and then AFTER the new view is visible, perform your NSURLRequest.
Adding a UIActivityIndicator may also help your users realize that your app was snappy and responsive, and the delay they are experiencing is from the internet.
Perhaps the easiest way to fix this would be to move your code over to
- (void)viewDidLoad {}

Is it possible to "pause" a thread and let another operation proceed first?

I set up 2 UIWebViews, the first is controlling the second. They are communicating though ajax requests.
I want to load a website in the second WebView and then proceed with other tasks. Unfortunately this is crashing. It is crashing because the Web Thread is being occupied by the first right after it gets a response. The second has no time to load the web page and causes a deadlock.
I want to delay the response until the second WebView has fully loaded the web page. Currently the second WebView starts loading right after the first WebView gets and response (thats when the Web Thread is being released).
Is it possible to "suspend"/"pause" the current (first WebView) execution until the second WebView has finished loading? This means to start the execution of the second WebView as well.
events:
First WebView sends command to load web page (using synchronous AJAX command)
Web Thread blocked by task of first WebView
Execution of command and computation of Response
Returning Response
Second WebView starts Loading of web page
deadlock
I want event 5 to be before event 4. Is this possible?
Solution:
As you can read in the comments I've solved my problem by making then work concurrently. Basically I had to make use of the Grand Central Dispatch (GCD). Another option would be to implement it with NSOperationQueues which gives you more control about the flow of execution, but tends to be more complicated to implement.
helpful literature:
Apple: Concurrency Programming Guide
Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial
How To Use NSOperations and NSOperationQueues
Now, this is may require some tweaking, but it should give you a good place to start.
Basically, we create a concurrent GCD queue and dispatch 2 async calls to load HTML strings with the contents of your 2 different URLS.
When the requests complete they will load their html strings into your web views. Note that the first UIWebView will only load its data if the second UIWebView has already been loaded.
__weak ViewController *bSelf = self;
dispatch_queue_t webQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(webQueue, ^{
NSError *error;
bSelf.html1 = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://google.com"] encoding:NSASCIIStringEncoding error:&error];
if( !bSelf.secondLoaded)
{
dispatch_sync(dispatch_get_main_queue(), ^{
[bSelf.webView1 loadHTMLString:bSelf.html1 baseURL:nil];
});
}
});
dispatch_async(webQueue, ^{
NSError *error;
bSelf.html2 = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://amazon.com"] encoding:NSASCIIStringEncoding error:&error];
bSelf.secondLoaded = YES;
dispatch_sync(dispatch_get_main_queue(), ^{
[bSelf.webView2 loadHTMLString:bSelf.html2 baseURL:nil];
if( bSelf.html1 != nil )
{
[bSelf.webView1 loadHTMLString:bSelf.html1 baseURL:nil];
}
});
});
Yes, the two best ways to do this would be to use either Grand Central Dispatching (GCD) or NSOperation and NSOperationQueue.
The explanation of this is quite long, but I would direct you to read something like this. You can find a lot of other resources if you search for these terms in google.
Have you tried something like this?
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView.delegate = self;
self.webView2.delegate = self;
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"yourURL"]]];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if (webView == self.webView)
{
if (!self.webView.isLoading)
{
[self.webView2 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"yourURL"]]];
}
}
}

The same situation,Second time be rejected because of used MBProgressHUD :(

Reasons for Rejection: The activity indicator spins indefinetely and the user can't access the content
The same situation,Second time be rejected because of used MBProgressHUD.
Who can tell me Uploaded to appstore app would be any different? I done various tests, such a problem did not appear in the local.
-----------------------------in my controller-----------------------------------
- (void)downloadList
{
if (isLoading) {
return;
}
isLoading = YES;
//do something......
//show the progressbar based on MBProgressHUD
[[MyDelegate getAppDelegate] showProgressBarForTarget:self whileExecuting:#selector(showProgressBarForLoading)];
}
}
- (void)showProgressBarForLoading
{
while (isLoading) {
//i++;
continue;
}
}
- (void)downloadListDidReceive:(XGooConnection*)sender obj:(NSObject*)obj
{
//do something......
isLoading = NO;
}
-----------------------------in my AppDelegate-------------------------------
- (void)showProgressBarForTarget:(id)target whileExecuting:(SEL)theSelector
{
UIViewController *controller = [splitViewController.viewControllers objectAtIndex:0];
HUD = [[MBProgressHUD alloc] initWithView:controller.view];
[controller.view addSubview:HUD];
HUD.delegate = self;
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:theSelector onTarget:target withObject:nil animated:YES];
}
-----------------------------Reasons for Rejection detail-------------------------------------
The most recent version of your app has been rejected........
Reasons for Rejection:
The steps to reproduce are:
Launch the app
Select the Menu button at the top left corner
Select a menu item
The activity indicator spins indefinetely and the user can't access the content
First off, the reason for this rejection is likely improper usage of MBProgressHUD, not MBprogressHUD itself.
If this only occurs during app store testing, try running the app in Release configuration. There also might be networking conditions there, that you haven't anticipated. Perhaps this only occurs when there is a network error (airplane mode?). Are you setting isLoading = NO when a network error occurs?
FWIW, there is a much better way to show / hide the HUD for asynchronous requests. Pooling a flag in a while loop like this is extremely inefficient. Look at the NSURLConnection example in the MBProgressHUD demo app.

Resources