When I use [SVProgressHUD show], delay 2 seconds, then use [SVProgressHUD dismiss], the HUD dismiss.
But when I use [SVProgressHUD showErrorWithStatus:#"test"], the hud can' show.
Can someone stay me why?
- (void)viewDidLoad {
[super viewDidLoad];
[SVProgressHUD show];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
[SVProgressHUD showErrorWithStatus:#"test"];
});
}
The SVProgressHUD.showInfoWithStatus will hide the message after some time. so probably second HUD won't show cause it is still remove the first one. [SVProgressHUD show];
If you wanna update don't call dismiss [SVProgressHUD dismiss];
Remove the line:
[SVProgressHUD dismiss];
Use only:
[SVProgressHUD showErrorWithStatus:#"test"];
I think this is helpful for you.
Related
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];
}];
});
}];
In the MBProgressHud documentation it states to use this inside of a dispatch like so:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
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];
});
});
Which is completely understandable considering you don't want it to boggle up the main thread. But could I just do this instead when using a block:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error)
{
}
else
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
}];
Or would I still have to use dispatch?
Change your code to be like this:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
NSLog(#"Am I running on the main thread: %#", [NSThread isMainThread] ? #"YES": #"NO");
if (error)
{
}
else
{
}
}];
if it logs "YES" then you don't need to run [MBProgressHUD hideHUDForView:self.view animated:YES]; on the main thread, otherwise you need to use
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
Update:
blocks are run on whatever thread they've been called from, notice following example:
- (void)viewDidLoad {
[super viewDidLoad];
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this is running on the concurrent thread, so does completionBlock() as it has been called on a concurrent thread.
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
completionBlock();
});
}
So Basically the result of above will be "Is running on the main thread? NO"
Again I have exact same call on viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
But this time, I'm calling completionBlock of longRunningProcessWithCompletionBlock on the main thread as follow:
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
//notice the difference, this time we are calling the completionBlock on the main thread, so that block will be running on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
});
}
This time because we have called the completion block on the main thread, the result will be Is running on the main thread? YES
So in a nutshell, block does not guarantee that they are getting executed on the main thread! but they can guarantee that they will be executed on whatever thread they've been called from.
In your case Parse.com developers are calling the completion handler block of deleteInBackgroundWithBlock on the main thread and that's why you saw that log was "yes".So you just need to call [MBProgressHUD hideHUDForView:self.view animated:YES]; without dispatch_async(dispatch_get_main_queue(), ^{ }); (as it is already on the main thread and this is an extra unnecessary step)
I am utilizing MBProgressHUD along with STTwitter...I call MBProgressHUD and then load the twitter feed off the main thread and hide the HUD thereafter. Unfortunately, the HUD hides as soon as it receives a response and not necessarily after the data has completely downloaded. Is there a solution to this? This also occurs with webviews elsewhere in the app. Thanks!
[[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Sets the auth key (user or app) for the RMACC Twitter Feed
STTwitterAPI *twitter = [STTwitterAPI twitterAPIOSWithFirstAccount];
[twitter verifyCredentialsWithSuccessBlock:^(NSString *username) {
[twitter getUserTimelineWithScreenName:#"RMACCNewsNotes" count: 10 successBlock:^(NSArray *statuses) {
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
self.twitterFeed =[NSMutableArray arrayWithArray:statuses];
[self.tableView reloadData];
}
errorBlock:^(NSError *error){
}];
} errorBlock:^(NSError *error) {
[self twitterselfauthrequest];
}];
});
}
I would suggest using SVProgressHUD which is easy to use, implements Singleton, and with much more functionality and control
Move your HUD hiding code inside successBlock as follows
successBlock: ^(NSArray *statuses) {
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
self.twitterFeed =[NSMutableArray arrayWithArray:statuses];
[self.tableView reloadData];
}
Try changing dispatch_async to dispatch_sync.
I think this might be happening because of some issues dispatch_async has on main thread.
I'm currently calling storeViewController loadProductWithParameters via dispatch_async . Is it possible to set a timeout value so it only tries to fetch the results for X seconds and then gives up?
I implemented my own timeout by using with the class method below instead of calling loadProductWithParameters directly. It times out thanks to a dispatch_after and __block variable.
+ (void)loadProductViewControllerWithTimeout:(NSTimeInterval)timeout
storeKitViewController:(SKStoreProductViewController *)storeKitViewController
parameters:(NSDictionary *)parameters
completionHandler:(void (^)(BOOL result, NSError *error))completionHandler {
__block BOOL hasReturnedOrTimedOut = NO;
[storeKitViewController loadProductWithParameters:parameters completionBlock:^(BOOL result, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!hasReturnedOrTimedOut) {
hasReturnedOrTimedOut = YES;
if (completionHandler) completionHandler(result, error);
}
});
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!hasReturnedOrTimedOut) {
hasReturnedOrTimedOut = YES;
if (completionHandler) completionHandler(NO, nil); // Or add your own error instead of |nil|.
}
});
}
My latest app update got rejected by Apple because loadProductWithParameters never called its completionBlock and stopped my users from buying songs on iTunes... Hope this helps.
I have acomplished it like so:
__block BOOL timeoutOrFinish = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(!timeoutOrFinish) {
timeoutOrFinish = YES;
[self dismissAndShowError];
}
});
[storeViewController loadProductWithParameters:parameters completionBlock:^(BOOL result, NSError * _Nullable error) {
if(timeoutOrFinish) {
return;
}
timeoutOrFinish = YES;
//[[NetworkManager sharedManager] showNetworkActivityIndicator:NO];
if(error) {
[self dismissAndShowError];
}
}];
[self.view.window.rootViewController presentViewController:storeViewController animated:YES completion:nil];
where dismissAndShowError method runs dismissViewControllerAnimated and shows alert with an error.
Basically, you have a separate timer (30 seconds in my case) that switches a flag. After that time, if store has still not been loaded, I close it and display an error. Otherwise, completion is called (on cancel, finish and error) and handles all actions according to the status.
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;