Cant dismiss UIAlertView, buttons not clickable - ios

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;

Related

Downloading data when iOS app goes in background

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!

data is not loading in tableView and not updating profile

I am new in objective C.Execute this Program ON Clicking on button"Click here to sell your Gold now"
It will lunch you to dashboard.In dashboard select Transaction History.I have fetchNewHothistory function in TransactionHistoryViewController.m file responsible for fetching data from url and displaying it onto the viewcontroller file
-(void)fetchNewHothistory
{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval:1.0
target: self
selector:#selector(mytimerChecking:)
userInfo: nil repeats:NO];
NSMutableDictionary *paramDict=[NSMutableDictionary dictionary];
[paramDict setObject:#"ios" forKey:#"request"];
[paramDict setObject:[NSString stringWithFormat:#"%#",currentUser.user_id] forKey:#"user_id"];
[paramDict setObject:[NSString stringWithFormat:#"%#",self.currentLimitNew] forKey:#"limit_start"];
[GeneralWebservices webserviceMainSplashCall:paramDict webserviceName:Webservice_TransactionHistory OnCompletion:^(id returnDict, NSError *error) {
if ([returnDict[#"success"] intValue] ==1)
{
// UIAlertView* alert = [[UIAlertView alloc] init];
// [alert setTitle:#"RECORD FOUND"];
// // [alert setMessage:returnDict[#"message"]];
// [alert addButtonWithTitle:#"OK"];
// [alert show];
[history addObjectsFromArray:returnDict[#"data"]];
self.currentLimitNew=[NSString stringWithFormat:#"%#",returnDict[#"limit_start"]] ;
[historyTableView reloadData];
}
else
{
UIAlertView* alert = [[UIAlertView alloc] init];
[alert setTitle:#"RECORD FOUND"];
//[alert setMessage:returnDict[#"message"]];
[alert addButtonWithTitle:#"OK"];
[alert show];
}
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
[historyTableView.pullToRefreshView stopAnimating];
[historyTableView.infiniteScrollingView stopAnimating];
}];
}
Issue -1
Record found but nothing displayed on Viewcontroller on selecting the Transaction History.The How to display record onto viewcontroller?
Issue -2
In on selecting Profile the ProfileViewController.m is executed .On updating the user profile .It does not updates .It remain buffering .How to perform profile update .
you can download the project from this link .https://drive.google.com/file/d/1daW4veZAI21b8TqKFHauSFTboHKJceaG/view?usp=sharing
Try this:
dispatch_async(dispatch_get_main_queue(), ^{
[historyTableView reloadData];
});

How to avoid Callback hell in iOS?

How do I avoid callback hell in the following or similar cases,
[self saveSomethingToTheServerWithCompletion:^(BOOL saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (saveSucceeded) {
[self showAlertControllerWithTitle:#"Message" message:#"Save successful."];
} else {
[self showAlertControllerWithTitle:#"Message" message:#"Save failed."];
}
});
}];
});
}];
My understanding is that dismissViewControllerAnimated:completion: and showAlertControllerWithTitle:message: must be executed on the main thread.
The question was fairly vague, so I'm taking a best guess at what you're asking. The only thing I can think of is that you're not happy with the nested calls. You can tidy that up with methods to an extent. e.g. you know that showing an alert always needs to be done on the main thread, so move the main thread code to your alert method:
[self saveSomethingToTheServerWithCompletion:^(BOOL saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:^{
NSString *message = saveSucceeded ? #"Save successful." : #"Save failed.";
[self showAlertControllerWithTitle:#"Message" message:message];
}];
});
}];
- (void)showAlertControllerWithTitle:(NSString *)title message:(NSString *)message {
dispatch_async(dispatch_get_main_queue(), ^{
//Show alert;
}
}
Additionally, you could consider removing the completion block from the dismissViewControllerAnimated call. i.e. does your alert being shown really depend on whether or not the view controller has dismissed?
[self saveSomethingToTheServerWithCompletion:^(BOOL saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *message = saveSucceeded ? #"Save successful." : #"Save failed.";
[self showAlertControllerWithTitle:#"Message" message:message];
[self dismissViewControllerAnimated:YES completion:nil];
});
}];
Ultimately though, sometimes you just have to nest callbacks. There's nothing really wrong with that.
Edit: I also replaced your if statement with a ternary one without really thinking about it. Not sure if you count that as "callback hell".
You can try something like this
[self saveSomethingToTheServerWithCompletion:^(BOOL saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *strMsg = saveSucceeded ? #"Save successful." : #"Save failed.";
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:^{
}];
});
}];
[self showAlertWithTitle:#"Message" message:strMsg actions:#[okAction]];
});
}];
- (void)showAlertWithTitle:(NSString * _Nonnull)title message:(NSString * _Nonnull)msg actions:(NSArray * _Nonnull)actions {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
for (UIAlertAction *action in actions) {
[alertVC addAction:action];
}
[self presentViewController:alertVC animated:true completion:nil];
}
So on callback it will show alert with message and on ok button top alert will dismiss and also dismiss the presented view controller. So it will be on main thread.
You don't have to wrap dismissViewControllerAnimated: with callback in main thread because it's already in main thread, also try to avoid duplicated code:
[self saveSomethingToTheServerWithCompletion:^(BOOL saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:^{
NSString *messageContent = saveSucceeded ? #"Save successful." : #"Save failed.";
[self showAlertControllerWithTitle:#"Message" message:messageContent];
}];
});
}];

Initialize UITableview with Contents from XML but can't show progress with UIAlertview

I am writing an app, which is getting data from the net using XML. It is a master-detail-app which is fetching data for the master-table and after selecting one item from the master-Table it fills the data for the detailview(s) using another network-access.
I would like to present an alert in order to show the user that the app is busy accessing the net or busy calculating. So I would present the view from the UIAlertController before the calculation / network access starts and dismiss the view when the activity has completed
Problem is: I don't know where to put this call to show / dismiss the UIAlertcontroller view.
Putting the activity code into ViewWillAppear shows and dismisses the alertview BEFORE the network-access... Putting everything into ViewDidLoad seems not the way to go.
- (void) viewWillAppear:(BOOL)animated
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Working"
message:#"Working on it"
preferredStyle:UIAlertControllerStyleAlert];
self.objects = [[NSMutableArray alloc] init];
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"Accounts"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
NSURL *url;
if ([self getSession])
{
NSMutableString *URLstring = [NSMutableString stringWithString:#"https://XXXXXXXXXXXXXXX.xml?session="];
[self presentViewController:alert animated:YES completion:nil];
[URLstring appendString:[[DataStore getData]SessionString]];
url = [NSURL URLWithString:URLstring];
self.myXXXXXXParserAlleKontenXMLDelegate = [[KontenParserDelegate alloc] init];
self.xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
self.xmlParser.delegate = self.myfxbookParserAlleKontenXMLDelegate;
if ([self.xmlParser parse])
{
[self.objects removeAllObjects];
for(int i=0; i< [self.XXXXXXXXXrAlleKontenXMLDelegate.allAccounts count];i++)
{
[self.objects addObject : [self.myfxbookParserAlleKontenXMLDelegate.allAccounts objectAtIndex:i]];
}
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey: #"name" ascending: YES];
[self.objects sortUsingDescriptors:#[sort]];
[self.tableView reloadData];
[self dismissViewControllerAnimated:YES completion:nil];
// Do any additional setup after loading the view, typically from a nib.
}
}
}
Added After some discussions I added some new framework "SVProgressHUD" and entered the following code into "ViewWillAppear" where [self parserstuff] contains all the XML parsing..
[SVProgressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self parserstuff];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
});
This leaves me with a HUD-Display showing up while parsing XML but the resulting Tableview does not show data but is empty... After switching to DISPATCH_SYNC it works.
Solution would be
1) Put code in ViewDidLoad
2) encapsulate Code between "SVProgressHUD show" and "dismiss" with dispatch_async
as in
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD show];
[self parserstuff];
[self fillComparatorParameters:nil];
[self.tableView reloadData];
[SVProgressHUD dismiss];
// }];
});

UIAlertView in another UIAlertView blocks keyboard input in the whole app

The problems is that the iOS keyboard sometimes does not react anymore. So no input is possible.
If I uncomment the second UIAlertView in clickedButtonAtIndex it works fine.
The reason might be somewhere else? I have no idea ...
Thank you,
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if(buttonIndex == 1)
{
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// User exists already
RequestObject * requestObject = [RequestObject sharedRequestObject];
[requestObject loginWithEmail:self.emailTextField.text andPassword:self.passwordTextField.text
success:^(UserVO *user) {
// ...
} failure:^(ErrorVO *error) {
// User does not exist, create a new one
if (error.code == ERROR_USER_UNKNOWN) {
// ...
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"ALERT_CAPTION_ERROR", "<Error>") message:[ErrorCode getErrorMessage:error] delegate:nil cancelButtonTitle:NSLocalizedString(#"ALERT_BUTTON_TEXT_OK", "<Ok>") otherButtonTitles:nil];
[alert show];
}
}];
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
}
}
The App uses storyboards
[self.window makeKeyAndVisible] is set
When MBProgressHUD starts then you are not able to do any interaction with UI. So it is not possible to do it without any built-in changes of MBProgressHUD.

Resources