How to avoid Callback hell in iOS? - 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];
}];
});
}];

Related

Async call causing EXC_BAD_INSTRUCTION crash

The app I'm working on is using a function that is working fine but blocks the main thread. I am attempting to add a loading spinner using SVProgressHUD and that requires I call my function asynchronously in order to display the spinner. As soon as I call the function asynchronously however the app crashes with EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0 The only change I have made to the function is to invoke the popViewControllerAnimated lines on the main thread. Why is running this code on a new thread causing it to crash and how can I fix it?
Calling code:
-(void) _doSaveDataPoint {
[SVProgressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self _saveDataPoint];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
});
}
_saveDataPoint function. popViewController called on main thread near the end of this code:
-(void) _saveDataPoint {
NSString *errorMsg = nil;
if ([[myLegend type] isEqualToString:#"PIN"]) {
if ([myNodes count]==0) {
errorMsg = #"Please make sure you have added one point on to the map to continue.";
}
}
else if ([[myLegend type] isEqualToString:#"POLYGON"]) {
if ([myNodes count]<3) {
errorMsg = #"Please make sure you have at least 3 points set before continuing.";
}
}
else {
if ([myNodes count]<2) {
errorMsg = #"Please make sure you have at least 2 points set before continuing.";
}
}
if (errorMsg !=nil) {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"Not enough points"
message:errorMsg
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
// Just dismiss
}];
[alertController addAction:okAction];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:alertController animated:YES completion:nil];
});
return;
}
ClientLegendDataPointBounds *bounds = [[ClientLegendDataPointBounds alloc] init];
int count = 0;
GeoPoint *first = nil;
NSMutableDictionary *attr = [[NSMutableDictionary alloc] init];
for (_EditAnnotation *anno in myNodes) {
GeoPoint *point = [[GeoPoint alloc] initWithLatitude:[anno coordinate].latitude andLongitude:[anno coordinate].longitude];
[bounds expand:point];
if (count==0) {
first = point;
count++;
continue;
}
NSString *xKey = [NSString stringWithFormat:#"x%d",count-1];
NSNumber *xCoord = [NSNumber numberWithDouble:[point latitude ]];
NSString *yKey = [NSString stringWithFormat:#"y%d",count-1];
NSNumber *yCoord = [NSNumber numberWithDouble:[point longitude]];
[attr setObject:xCoord forKey:xKey];
[attr setObject:yCoord forKey:yKey];
count++;
}
if (count>0) {
NSString *pointCount = [NSString stringWithFormat:#"%d", count-1];
[attr setObject:pointCount forKey:#"pointCount"];
}
[self _setBarThemeDefault];
if (myDataPoint==nil) {
myDataPoint = [myLegend addDataPoint:[NSNumber numberWithLongLong:[DateTime currentTimeInMillis]] title:#"" description:#"" latitude:[first latitude] longitude:[first longitude] attributes:attr type:[myLegend type] bounds:bounds];
dispatch_async(dispatch_get_main_queue(), ^{
[[self navigationController] popViewControllerAnimated:NO];
});
[myHandler newItemCreated:myDataPoint];
} else {
[myDataPoint setAttributes:attr];
[myDataPoint setBounds:bounds];
[myDataPoint setLatitude:[first latitude]];
[myDataPoint setLongitude:[first longitude]];
[myDataPoint setModified:[NSNumber numberWithLongLong:[DateTime currentTimeInMillis]]];
[myDataPoint update];
dispatch_async(dispatch_get_main_queue(), ^{
[[self navigationController] popViewControllerAnimated:YES];
});
[myHandler itemUpdated:myDataPoint];
}
[self _finishSurveyLog:[SurveyLogItem ACT_SAVE_SPATIAL_CONST]];
[self _saveUserLocation];
}
I don't know exactly the plugin but could it be that the plugin itselfs dispatches the ui stuff to the main queue? So you don't have to dispatch the call to the main queue by yourself. Take a look at the source code:
SVProgressHUD.m

App stops after calling performSegue inside Touch ID block

Hi I’m experiencing a strange behavior I really don’t understand.
I present a touch ID identification to the user and if he’s authorized I
call a [self performSegueWithIdentifier: #"callCustomSegue" sender:self];
inside the block in this way:
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
[self performSegueWithIdentifier: #"callCustomSegue" sender:self];
Then the app stops for several seconds (at least 3-4) then next ViewController is presented.
The perform called by “callCustomSegue” does this:
- (void) perform {
src = (UIViewController *) self.sourceViewController;
dst = (UIViewController *) self.destinationViewController;
[src.view addSubview:dst.view];
}
I don’t understand what’s happening between the identification on touch ID and the performSegueWithIdentifier
and why the app stops.
If I bypass the touch ID and just call the performSegueWithIdentifier works immediately as I would expect.
If I put in the touch ID block:
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
authenticated = YES;
[self showMessage:#"Authentication is successful" withTitle:#"Success"];
}
where showMessage does this:
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* cancel = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
if (authenticated) {
[self performSegueWithIdentifier: #"callCustomSegue" sender:self];
}
if (!authenticated) {
[self touchID];
}
}];
after tapping OK the next ViewController is called immediately.
So the question is: why I can’t call performSegue within the touch ID block and get an immediate response?
Any idea where I’m wrong?
Thank you so much.
You should perform all UI related activities on the main queue. The reply block for the touchID process is not guaranteed to be executing on the main queue. In fact you can almost guarantee that it won't be.
You should have -
if (success) {
authenticated = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier: #"callCustomSegue" sender:self];
});

UIImagePickerController not presenting in iOS 8

Is anyone else having an issue with UIImagePickerController in iOS 8? The method below works perfectly well in iOS 7 on an iPad, but I get the following error when I run this in XCode 6 (Beta 3 or 4) when I try to present the picker (last line). If it matters, the selection of the sourceType is coming from an alertView that is presented in the same place.
Warning: Attempt to present <UIImagePickerController: 0x7c0ae400> on <CAGUCreateContactViewController: 0x7bf61a00> which is already presenting (null)
Method to open imagePicker.
- (void)openPhotoPicker:(UIImagePickerControllerSourceType)sourceType
{
if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType];
if ([availableMediaTypes containsObject:(NSString *)kUTTypeImage]) {
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.modalPresentationStyle = UIModalPresentationFullScreen;
imagePickerController.sourceType = sourceType;
imagePickerController.mediaTypes = #[(NSString *)kUTTypeImage];
imagePickerController.delegate = self;
self.imagePickerController = imagePickerController;
if (sourceType == UIImagePickerControllerSourceTypeCamera) {
[self presentViewController:self.imagePickerController animated:YES completion:nil];
} else {
if (self.popoverVC) {
[self.popoverVC dismissPopoverAnimated:YES];
self.popoverVC = nil;
}
self.popoverVC = [[UIPopoverController alloc] initWithContentViewController:imagePickerController];
[self.popoverVC presentPopoverFromRect:self.nameAndPicCell.picture.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
}
}
}
I think this is because in iOS 8, alert views and action sheets are actually presented view controllers (UIAlertController). So, if you're presenting a new view controller in response to an action from the UIAlertView, it's being presented while the UIAlertController is being dismissed. I worked around this by delaying the presentation of the UIImagePickerController until the next iteration of the runloop, by doing this:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self openPhotoPicker:sourceType];
}];
However, the proper way to fix this is to use the new UIAlertController API on iOS 8 (i.e. use if ([UIAlertController class]) ... to test for it). This is just a workaround if you can't use the new API yet.
I agree with Ben Lings issue detection. I would suggest a simpler solution in case when using UIActionSheet. I simply moved my code that reacts on Action Sheet selection from:
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex;
{
// my code
}
into:
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex; // after animation
{
// my code
}
This way app is guarantied that code will be executed AFTER UIActionSheet animation finishes.
Since UIAlertView has similar delegate method:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex; // after animation
{
// my code
}
I suppose that similar solution may apply.
Here is a solution that worked for me
if([[[UIDevice currentDevice] systemVersion] floatValue]>=8.0)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self presentViewController:cameraUI animated:NO completion:nil];
}];
}
else{
[controller presentViewController:cameraUI animated:NO completion:nil];
}
Remember to alloc cameraUI
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
Build and Go!
I was facing the same problem in iOS 8.
Then I saw the change log of the latest update to iOS i.e. 8.0.2 on the device.
It is mentioned in this update that_
"Fixes an issue that prevented some apps from accessing photos from Photo Library"
So test your app using XCode 6 on device with iOS 8.0.2 version it will work fine
Don't test it on iOS 8.0 simulator.
This helped me, hope the same for you.
UIImagePickerController *imagePickerController= [[UIImagePickerController alloc] init];
[imagePickerController setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
// image picker needs a delegate so we can respond to its messages
[imagePickerController setDelegate:self];
self.shouldCallViewWillAppear = NO;
if(IS_IOS8)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Place image picker on the screen
[self presentViewController:imagePickerController animated:YES completion:nil];
}];
}
else
{
[self presentViewController:imagePickerController animated:YES completion:nil];
}
You can dismiss the presented view controller (if any) by using
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
This worked for me.
All you need to do is dismiss already presented ViewController:
if (self.presentedViewController) {
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
}
[self openPhotoPicker:sourceType];
If it still produces error, put openPhotoPicker: to completion handler
I simply did this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,
(unsigned long)NULL), ^(void) {
[self retractActivePopover];
dispatch_async(dispatch_get_main_queue(), ^ {
_activePopover=imagePickerPopover;
UIBarButtonItem *callingButton = (UIBarButtonItem*) sender;
[imagePickerPopover presentPopoverFromBarButtonItem:callingButton permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
});
});
On iOS 8 you should use the new API:
if (SYSTEM_VERSION_IOS_8) {
self.imagePickerController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popPC = self.imagePickerController.popoverPresentationController;
popPC.barButtonItem = self.popoverItem;
popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
[self presentViewController:self.imagePickerController animated:YES completion:nil]
}
I recommend you watch the 2014 WWDC session 228 a look in side presentation controllers
I went through a lot of pain coming up with a solution which works on both iPad and iPhone, this is the final code which some of it comes from comments of other people:
the code has some bugs but it's a very good place to start :)
definitions :
__weak IBOutlet UIButton *attachButton;
UIImage *image;
button's action :
- (IBAction)doAttach:(id)sender {
UIActionSheet *action = [[UIActionSheet alloc] initWithTitle:#"Select image from" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"From library",#"From camera", nil] ;
[action showInView:self.view];
}
#pragma mark - ActionSheet delegates
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if( buttonIndex == 1 ) {
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authStatus == AVAuthorizationStatusAuthorized)
{
NSLog(#"%#", #"You have camera access");
}
else if(authStatus == AVAuthorizationStatusDenied)
{
NSLog(#"%#", #"Denied camera access");
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if(granted){
NSLog(#"Granted access to %#", AVMediaTypeVideo);
} else {
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#“no camera access“
message: #“if you need to use camera in this application go to settings -> appName -> and turn on camera.”
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#“ok” style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
NSLog(#"Not granted access to %#", AVMediaTypeVideo);
return ;
}
}];
}
else if(authStatus == AVAuthorizationStatusRestricted)
{
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#“no camera access“
message: #“if you need to use camera in this application go to settings -> appName -> and turn on camera.”
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#“ok” style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
NSLog(#"%#", #"Restricted, normally won't happen");
}
else if(authStatus == AVAuthorizationStatusNotDetermined)
{
NSLog(#"%#", #"Camera access not determined. Ask for permission.");
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if(granted){
NSLog(#"Granted access to %#", AVMediaTypeVideo);
} else {
NSLog(#"Not granted access to %#", AVMediaTypeVideo);
return ;
}
}];
}
else
{
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#“No camera access“
message: #“error accusing camera”
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#“ok” style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
return;
//NSLog(#"%#", #"Camera access unknown error.");
}
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
UIImagePickerController *pickerView =[[UIImagePickerController alloc]init];
pickerView.allowsEditing = YES;
pickerView.delegate = self;
pickerView.sourceType = UIImagePickerControllerSourceTypeCamera;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
[ self.presentedViewController dismissViewControllerAnimated:YES completion:nil ];
pickerView.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popPC = pickerView.popoverPresentationController;
popPC.sourceView = attachButton;
popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
[self presentViewController:pickerView animated:YES completion:nil];
} else {
[self presentModalViewController:pickerView animated:YES ];
}
}
}else if( buttonIndex == 0 ) {
ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
switch (status) {
case ALAuthorizationStatusRestricted:
case ALAuthorizationStatusDenied:
{
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#“no access to library”
message: #“if you wish to access photos in this app go to settings -> appName-> and turn on photos .”
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#“ok” style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
}
break;
default:
{
UIImagePickerController *pickerView = [[UIImagePickerController alloc] init];
pickerView.allowsEditing = YES;
pickerView.delegate = self;
[pickerView setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
[ self.presentedViewController dismissViewControllerAnimated:YES completion:nil ];
pickerView.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popup = pickerView.popoverPresentationController;
popup.sourceView = attachButton;
popup.permittedArrowDirections = UIPopoverArrowDirectionAny;
[self presentViewController:pickerView animated:YES completion:nil];
} else {
[self presentModalViewController:pickerView animated:YES ];
}
}
break;
}
}
}
#pragma mark - PickerDelegates
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
[self dismissModalViewControllerAnimated:true];
UIImage * img = [info valueForKey:UIImagePickerControllerEditedImage];
image = img;
}
performSelector:withObject:afterDelay solved my problem.
also didDismissWithButtonIndex do the trick.
Max
Here's a Xamarin solution. What worked for me was to add my actions to a Dismissed event handler.
this.btnPhoto.TouchUpInside += (sender, e) =>
{
actionSheet = new UIActionSheet ("Add Photo");
actionSheet.AddButton ("Take Photo");
actionSheet.AddButton ("Select from Library");
actionSheet.AddButton ("Cancel");
actionSheet.DestructiveButtonIndex = -1; // red
actionSheet.CancelButtonIndex = 3; // black
actionSheet.Clicked += delegate(object a, UIButtonEventArgs b)
{
actionSheet.Dismissed += (object aSender, UIButtonEventArgs dismissArgs) =>
{
switch (dismissArgs.ButtonIndex)
{
case 0:
showCamera ();
break;
case 1:
showPhotoLibrary ();
break;
}
};
};
actionSheet.ShowInView (view);
};

MBProgressHUD after selecting image in UIImagePickerController

I'm selecting an image in UIImagePickerController.
After the image was selected I'm firing another UIActionSheet to select from
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
_selected_image = info[UIImagePickerControllerEditedImage];
UIActionSheet *popup = [[UIActionSheet alloc] initWithTitle:#"" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:
#"Share this photo",
nil];
popup.tag = 2;
[popup showInView:[UIApplication sharedApplication].keyWindow];
}
When the user selects "Share" a long process is starting.
I want the MBProgressHUD to show during the process. I can't get progress HUD to show before stuff are starting to happen.
Tried two things:
- (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex {
if (popup.tag == 1)
{
...
}
else if (popup.tag == 2)
{
if (buttonIndex == 1) //cancel
{
_selected_image = nil;
}
else if (buttonIndex == 0)
{
[imagePicker dismissViewControllerAnimated:YES completion:NULL];
[self doSharePhoto];
return;
}
[imagePicker dismissViewControllerAnimated:YES completion:NULL];
}
}
doSharePhotos start running when the imagePicker is still showing.
Tried:
- (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex {
if (popup.tag == 1)
{
}
else if (popup.tag == 2)
{
if (buttonIndex == 1) //cancel
{
_selected_image = nil;
}
else if (buttonIndex == 0)
{
[imagePicker dismissViewControllerAnimated:YES completion:^{
[self doSharePhoto];
}];
return;
}
[imagePicker dismissViewControllerAnimated:YES completion:NULL];
}
}
and in this case the MBProgressHUD inside doSharePhoto doesn't apprear at all
** EDIT **:
The code that starts the HUD is in [self doSharePhoto]:
- (void)doSharePhoto {
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
HUD.labelText = #"Please wait...";
HUD.delegate = self;
...
}
Any thoughts?
Does your doSharePhoto method perform the long running operation inside a seperate thread? If not it's blocking the main thread so , MBProgressHUD doesn't get a chance to show. Your doSharePhoto must look like this,
- (void)doSharePhoto {
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do the long running operation here...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
});
});
}
EDIT: Problem might be with the view you add your HUD to,
Change
self.navigationController.view to
self.navigationController.visibleViewController.view
So,
[MBProgressHUD hideHUDForView:self.navigationController.visibleViewController.view animated:YES];

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