Downloading data when iOS app goes in background - ios

I have a app which downloads a huge amount of data (mostly images and document files) when it is installed for the first time. Currently, I'm only able to display the progress in a HUD. But I wish I could somehow allow the data to be downloaded when the app goes into background (or device gets locked). As the app is being targeted for devices running iOS 7.0 and above, I'm using NSURLSessionto download the data. I've gone through various threads here on Stackoverflow as well as a tutorial here. Even after making changes to my app as per the tutorial, my app does not continue the download. I tested it on an iPad. When the app is sent to background(or locked), the download is paused and resumes when the app comes to foreground.
I'm unsure if my approach is wrong or my implementation is flawed. Any help/advice is welcome.
The flow of the app is as follows: 'LoginViewController' calls an internal method downloadData which has an object of the SaveProjectData class that performs the task of downloading.
LoginViewController.m
#implementation LoginViewController
- (void)viewDidLoad {
}
- (IBAction)sumitButtonDidClick:(id)sender {
if ([self checkNetworkConnection] ==NotReachable) {
UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:#"Network Error" message:#"No internet Connection." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
} else {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
hud.label.text = NSLocalizedString(#"Downloading...", #"HUD loading title");
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
if ([self loginStatus]) {
NSString * userName = self.user_nameTxtField.text;
CallProjectAPI * callProjectAPIinst = [[CallProjectAPI alloc] init];
NSString * appStatus = [[NSUserDefaults standardUserDefaults] objectForKey:#"appStatus"];
if ([appStatus isEqualToString:#"N"]) {
[callProjectAPIinst dataFromJSONFile];
[callProjectAPIinst saveImageName];
} else {
[self doSomeWorkWithProgress];
[callProjectAPIinst callAPI];
[self doSomeWorkWithProgress];
[self downloadData];
}
hud.label.text = NSLocalizedString(#"Complete!", #"HUD completed title");
porjectVC = [self.storyboard instantiateViewControllerWithIdentifier:#"ProjectViewController"];
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self presentViewController:porjectVC animated:YES completion:nil];
[hud hideAnimated:YES];
});
[[NSUserDefaults standardUserDefaults] setObject:userName forKey:#"userName"];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
UIAlertView * alert=[[UIAlertView alloc] initWithTitle:#"Alert" message:#"User Name or Password is wrong." delegate:nil cancelButtonTitle:#"ok" otherButtonTitles:nil];
[alert show];
});
}
});
}
}
-(void)downloadData{
if ([self checkNetworkConnection] ==NotReachable) {
UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:#"Network Error" message:#"No internet Connection." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
} else {
NSString * status =[[NSUserDefaults standardUserDefaults] objectForKey:#"DownloadData"];
if (status == nil) {
SaveProjectData * saveProjectDataInst = [[SaveProjectData alloc] init];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveAboutImage];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveConstructionUpdateImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveLogImages];
[saveProjectDataInst saveSmallImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveFloorPlanImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveUnitPlanImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveMasterPlanImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveBrochurs];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveGalleryImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveAmenitiesImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveVideos];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveMapImage];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveBannerImage];
[self doSomeWorkWithProgress];
[saveProjectDataInst savewalkthrough];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveFlatImages];
[self doSomeWorkWithProgress];
[saveProjectDataInst saveEventImages];
[self doSomeWorkWithProgress];
[[NSUserDefaults standardUserDefaults] setObject:#"YES" forKey:#"DownloadData"];
[saveProjectDataInst getUpdatedListForEachProject];
}
}
}
- (void)doSomeWorkWithProgress {
progress += 0.049f;
dispatch_async(dispatch_get_main_queue(), ^{
// Instead we could have also passed a reference to the HUD
// to the HUD to myProgressTask as a method parameter.
[MBProgressHUD HUDForView:self.view].progress = progress;
});
}
#end
Another approach could be to provide a button that the user clicks and data is downloaded while the user can still continue to use the
device for other perposes. Any pointers as to how I can implement
it?

iirc you shouldn't rely on downloading things when your app is in the background because you dont know when it will be killed due to memory pressure or something else
here's the Apple documentation on it
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html#//apple_ref/doc/uid/TP40007072-CH2-SW7

The answer to this question(a similar one) was given here by #HimanshuMahajan
I'm adding to that answer and posting a solution that worked for me, tested it on an iPad successfully.
1) use following line in header file of ViewController
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
2) in ViewDidLoad assign UIBackgroundTaskIdentifier like:
self.backgroundTask = UIBackgroundTaskInvalid;
3) Use following line of code, here I am just keeping on getDataFromServer method inside beginBackgroundTaskWithExpirationHandler: block
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}];
/* Here your downloading Code, let say getDataFromServer method */
[self getDataFromServer]; // Its dummy method
/* Your downloading Code End Here */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
});
4) If you want to check time remaining to download data in background, include following line in applicationDidEnterBackground:(UIApplication *)application delegate method of AppDelegate:
NSLog(#"Background time remaining = %.1f seconds", [UIApplication sharedApplication].backgroundTimeRemaining);
Adding to the answer:
5) add the following code in applicationDidEnterBackground:(UIApplication *)application method to allow the background execution without time limit
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
Hope someone finds the answer useful!

Related

Update progress of MBProgressHUD

I have a scenario in which my app downloads a large chunk of data only for the first time and this happens in say ViewController1. I'm using a different class to download the data and another one to save the data. So my question is, how can I update the progress of MBProgressHUD object created in ViewController1 to display the progress to the user?
The approach that I've adopted is to use NSNotificationCenter to send notifications. I'm sending notifications at the end of methods (13) in the class that saves data.
Here's what I've been doing:
//ViewController1.h
#interface ViewController1 ()
{
MBProgressHUD *hud;
float progress;
}
#end
//ViewController1.m
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"downloadComplete"
object:nil];
}
- (IBAction)sumitButtonDidClick:(id)sender {
hud = [[MBProgressHUD alloc] init];
hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
hud.label.text = NSLocalizedString(#"Please wait...", #"HUD preparing title");
hud.minSize = CGSizeMake(150.f, 100.f);
[hud showAnimated:YES];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
progress = 0.0f;
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
DownloadClass * obj1 = [[DownloadClass alloc] init];
[obj1 downloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
}
- (void) receiveNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"downloadComplete"])
{
NSLog (#"Successfully received the download complete notification!");
progress += 7.7f;
//[hud setProgress:progress]; // won't work
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD HUDForView:self.view].progress = progress;
});
}
}
Update: I'm receiving the notifications posted by the class that saves
data
You have created a instance variable for MBProgressHUD as MBProgressHUD *hud; and you start progress on a sumitButtonDidClick method as below :
hud = [[MBProgressHUD alloc] initWithFrame:CGRectMake(0, -30, 320, 768) ];
hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
hud.label.text = NSLocalizedString(#"Please wait...", #"HUD preparing title");
hud.minSize = CGSizeMake(150.f, 100.f);
UIWindow *window=[[[UIApplication sharedApplication]delegate]window];
hud.center=window.center;
[window addSubview:hud];
[hud showAnimated:YES];
But increasing progress as Class method of MBProgressHUD as [MBProgressHUD HUDForView:self.view].progress = progress;.
Fix for the issue is below :
dispatch_async(dispatch_get_main_queue(), ^{
hud.progress = progress;
});
if you want to hide the progress you can use
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
According to the documentation for MBProgressHUD you appear to be creating the HUD incorrectly. The example they give, is as follows...
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
// Set the bar determinate mode to show task progress.
hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
hud.label.text = NSLocalizedString(#"Loading...", #"HUD loading title");
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
You are creating an instance and attaching it to UIWindow
The official documentation lists it like this:
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.label.text = #"Loading";
NSProgress *progress = [self doSomethingInBackgroundCompletion:^{
[hud hideAnimated:YES];
}];
hud.progressObject = progress;
You can find it here: https://github.com/jdg/MBProgressHUD

when Navigate the another page Need to add activity indicator

#import "LoginVC.h"
#import "HOMEVC.h"
#import "DashboardVC.h"
#interface LoginVC ()
#end
#implementation LoginVC
UIActivityIndicatorView *spinner ;
-(IBAction)login:(id)sender{
NSString *userUpdate =[NSString stringWithFormat:#"%#",[Usernamefileld text]];
NSString *userUpdate1 =[NSString stringWithFormat:#"%#",[PasswordField text]];
NSString *baseURL = [NSString stringWithFormat:#"http://192.168.1.200:8094/YazakiService.svc/LOGIN/%#/%#",userUpdate,userUpdate1];
NSURL *url = [NSURL URLWithString:[baseURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSError *error;
NSData *responseData =[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSMutableArray *serviceResponse=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&error];
NSLog(#"got response==%#", serviceResponse);
NSDictionary *template=[serviceResponse objectAtIndex:0];
NSString *test=[template objectForKey:#"ValidState"];
// NSString *test1=[template objectForKey:#"Userid"];
NSString *helloString = #"1";
// //
// NSString *helloString1 =#"LFY430";
if ([test isEqual:helloString]) {
[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
[self moveToView];
// UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Login Successfully" message:#"Correct Uername/Password" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
}
else{
UIAlertView *alert2=[[UIAlertView alloc]initWithTitle:#"Login Failed" message:#"Incorrect Uername/Password" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alert2 show];
[self alertView1:alert2 didDismissWithButtonIndex:alert2];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(150, 225, 20, 30)];
[spinner setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
spinner.color = [UIColor blackColor];
[self.view addSubview:spinner];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)moveToView{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
DashboardVC *initView = (DashboardVC*)[storyboard instantiateViewControllerWithIdentifier:#"Dashboardvc"];
[initView setModalPresentationStyle:UIModalPresentationFullScreen];
[spinner stopAnimating];
[self presentViewController:initView animated:NO completion:nil];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self moveToView];
}
- (void) alertView1:(UIAlertView *)alertView1 didDismissWithButtonIndex:(NSInteger)buttonIndex
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
LoginVC *initView = (LoginVC*)[storyboard instantiateViewControllerWithIdentifier:#"loginvc"];
[initView setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentViewController:initView animated:NO completion:nil];
}
-(void)threadStartAnimating:(id)data
{
[spinner startAnimating];
}
#end
here by i initialse the indicator and start and stop the spinner but its not woking for me...
any one help me to solve the issues how to add the activity indicator when navigation the another view
Thanks in Advance
[self.view addSubview:spinner];
At this line you add the activity indicator to the current ViewController view.
However once you navigate to other view
DashboardVC *initView = (DashboardVC*)[storyboard instantiateViewControllerWithIdentifier:#"Dashboardvc"];
[self presentViewController:initView animated:NO completion:nil];
the current view changes to the new ViewController's view.
The activity indicator isnt present in that view and therefore you wont see your activity indicator there.
To solve, you should again add activity indicator in the second ViewController's viewDidLoad method
In Dashboard VC
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(150, 225, 20, 30)];
[spinner setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
spinner.color = [UIColor blackColor];
[self.view addSubview:spinner];
}
A better solution would be to use third party progress huds which make
the loading indicator part absolutely easy like MBProgressHUD
You should add activityindicator on navigation controller so, it is remains on front when you navigate.
you should use MBProgressHud the great third party library.
Just put class in your project and import .h file in your class when you want to show activity indicator and then add HUD (activity indicator) like,
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
// in your case show hud on `self.navigationController.view`
// use main thread to show if required
and hide like,
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
Hope this will help :)
When you stop UIActivityIndicatorView then also hide it.
[spinner setHidesWhenStopped:YES];
EDIT:
If you are not sure about NSThread then just do like this.
if ([test isEqual:helloString]) {
//[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
[spinner startAnimating];
[self moveToView];
}
And when you want to stop it ten
[spinner stopAnimating];
[spinner setHidesWhenStopped:YES];
instead of using indicator , use svprogresshud.Check this link https://github.com/SVProgressHUD/SVProgressHUD.
if you want animated progressbar check this link https://github.com/cemolcay/GiFHUD

UIActivityIndicatorView not working right

I have the following initialization in viewDidLoad:
self.indicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[self.indicator setCenter:CGPointMake([[UIScreen mainScreen]bounds].size.width/2, [[UIScreen mainScreen]bounds].size.height/2)];
//self.indicator.hidden = YES;
[self.indicator setHidesWhenStopped:1];
self.indicator.color = [UIColor orangeColor];
self.indicator.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
Now if I add the following 2 lines, the indicator shows and spins:
[self.view addSubview:self.indicator];
//self.indicator.hidden = NO;
[self.indicator startAnimating];
Thats not what I want, because the indicator should first show when I call my method, lets call it pressButton. I would then do the following:
- (void)pressButton {
[self.view addSubview:self.indicator];
[self.indicator startAnimating];
dispatch_async(dispatch_get_main_queue(), ^{
WebAccess *web = [WebAccess new];
NSDictionary *dicResults = [web logon:self.email.text :self.password.text];
if([[dicResults valueForKey:#"StatusCode"] intValue] == 200){// ALL IS OK
appDelegate.accessToken = [dicResults valueForKey:#"AccessToken"];
dicResults = [web getAccount:appDelegate.accessToken];
NSLog(#"dicResults after getaccount: %#", dicResults);
if([[dicResults valueForKey:#"StatusCode"] intValue] == 200){//dicResults holds a new object now!
NSArray *usersArray = [dicResults valueForKeyPath:#"Account.Users"];
NSLog(#"usersArray: %#", usersArray);//CORRECT DATA RECEIVED: YES
if(usersArray.count){
[[appDelegate cdh]deleteAllFromEntityWithName:#"AccountData"];
[[appDelegate cdh]deleteAllFromEntityWithName:#"UserData"];//------------- REMOVE ALL WHEN LOADING NEW USER. AMYBE TOO QF. CHECK WITH DESIGNER
}else{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Error" message:#"No users found on this account!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
}
//more code........
appDelegate.users = [NSMutableArray arrayWithArray:[[appDelegate cdh] fetchUsers]];
if(appDelegate.users.count){
NSArray *currentUserArray = [NSArray arrayWithArray:[[appDelegate cdh]fetchCurrentUser]];
appDelegate.currentUser = [currentUserArray lastObject];
if(appDelegate.currentUser==nil)
appDelegate.currentUser = appDelegate.users[0];
}
[menu goToVC:1];
}
}else if([[dicResults valueForKey:#"StatusCode"] intValue] == 201){
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Wrong email or password!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
}
[self.indicator stopAnimating];
});
}
I could've sworn I had it working yesterday when testing, but now it doesn't. Whats weird is if i put those same two lines from my press button method into viewdidload, the indicator shows, but when I do it inside my method (but obv outside the dispatch) it never shows....
Any help appreciated
Spin your long running process off onto its own thread. Leave the main thread open for UI updates.
- (void)pressButton
{
[self.view addSubview:self.indicator];
[self.indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Code for your long running process to run in the background...
// Stop your activity indicator back on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.indicator stopAnimating];
});
});
}

Disabling UI interaction of iPad till the data is downloading on main thread in backend

I want to Disable UI interaction of iPad till the data is downloading on main thread in backend using Blocks
I m downloading the images at loading time
-(void)downLoadImageData{
[self ShowActivityIndicator];
[iOSNetwork getImages:ImageID andEvent:eventID
onCompletion:^(NSString* result,NSError* error)
{
dispatch_async(dispatch_get_main_queue(), ^{
if(error)
{
[self stopFetch:#"Error while Processing"];
}
else
{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[self stopFetch:result];
}
});
}];
}
-(void) stopFetch:(NSString*) result{
[self hideActivityIndicator];
//after downloading completed
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
if use entire application call
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
after downloading completion
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
another choice
[self.view setUserInteractionEnabled:NO];
after the completion
[self.view setUserInteractionEnabled:YES];
one more choice
self.navigationController.navigationBar.userInteractionEnabled=NO;
// perform other events also userInteractionEnabled=NO;
after the completion
self.navigationController.navigationBar.userInteractionEnabled=YES;
// perform other events also userInteractionEnabled=NO;
in your question
-(void)downLoadImageData{
[self ShowActivityIndicator];
[[UIApplication sharedApplication] beginIgnoringInteractionEvents]; // call here
[iOSNetwork getImages:ImageID andEvent:eventID
onCompletion:^(NSString* result,NSError* error)
{
dispatch_async(dispatch_get_main_queue(), ^{
if(error)
{
[self stopFetch:#"Error while Processing"];
}
else
{
[self stopFetch:result];
// not here
}
});
}];
}
You can use MBProgressHud for this. You need to add MBProgressHUD files into project.
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.HUD = [[MBProgressHUD alloc] initWithWindow:appDelegate.window];
[appDelegate.window addSubview:self.HUD];
- (void)showHUDWithText:(NSString *)labelText{
if (_isHUDAlreadyInProgress) {
return;
}
_isHUDAlreadyInProgress = TRUE;
[_HUD.superview bringSubviewToFront:_HUD];
self.HUD.labelFont = [UIFont systemFontOfSize:13.0];
//self.HUD.labelText = labelText;
[self.HUD show:TRUE];
}
- (void)hideHUD{
_isHUDAlreadyInProgress = FALSE;
[self.HUD hide:TRUE];
}

Cant dismiss UIAlertView, buttons not clickable

I have the following code where i show a MBProgress view and then run code in a separate thread. I then get a handle to the main thread and dismiss the spinner which works and then i show a UIAlertView. The UIAlertView loads fine however i can not click any of the buttons. If the alert view is outside of the dispatch block it works fine. Any ideas?
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
GamePlayManager *gameManager = [GamePlayManager alloc];
Session *sess = [Session sharedInstance];
//Add the last actor to the end of the list
NSMutableDictionary *connections = sess.connections;
[connections setObject:sess.secondActor forKey:[NSString stringWithFormat:#"%d",kLastFieldtag]];
BOOL result = [gameManager areAnswersCorrect:sess.connections startingActor:sess.firstActor endingActor:sess.secondActor];
NSString *display = #"Sorry incorrect. Please recheck your answers.";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Result"
message:display
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil];
if (result)
{
display = #"You are correct! You Won!";
if (sess.isMutiplayerGame)
{
[_gameCenterController endGame];
[self showGameOverScreen:YES isMultiplayer:YES];
}
else
{
[self showGameOverScreen:YES isMultiplayer:NO];
}
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
[alert show];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
[alert show];
});
}
});
This is likely an issue caused by a clash between the MBProgressHUD's animation and the UIAlertView's animation.
I've never used MBProgressHUD, but looking at the code on GitHub it seems they've already solved your problem. MBProgressHUD has a completionBlock property.
Code like this should work: (Warning: Untested)
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD HUDForView:self.view].completionBlock = ^{
[alert show];
};
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
MBProgressHUD fires its completionBlock after the view has finished its animation, so there should no longer be a conflict.
As a side note the MBProgressHUD method:
- (void)showAnimated:(BOOL)animated
whileExecutingBlock:(dispatch_block_t)block
onQueue:(dispatch_queue_t)queue
completionBlock:(MBProgressHUDCompletionBlock)completion;
seems like it would be a better fit for your code.
Declare the alert view outside the threads with block:
__block UIAlertView *alert;

Resources