I have a code in a class method that must show a UIAlertView on successful operation (it's a Facebook SDK handler).
But, the alert doesn't show up.
Here's the code:
-(void) postCompletionHandler:(FBRequestConnection *)connection
result:(id)
result error:(NSError*)error
{
if (!error) {
// Link posted successfully to Facebook
NSLog(#"result: %#", result);
UIAlertView *successAlert = [[UIAlertView alloc]
initWithTitle:#"FB Success!"
message:result
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[successAlert show];
}
}
I put a breakpoint inside the block and
[successAlert show];
reaches execution and successfully executes. The debugger also shows, that this code excuted from the main, GUI thread.
I can't even imagine what can be the problem here.
Any ideas?
Update: big thanks for everyone who participated, your help means so much for me.
Now I solved the problem. The problem was, as pointed by #rmaddy, in incorrect parameter result, given to a constructor of an alertView class.
In the future I'll investigate on exception handling techniques in Xcode to prevent such an obvious errors to ruin my code.
Related
I'm working on this login screens. I want the user to be notified when login fails. For example if the user enters the wrong credentials or the server which my app connects to is down it has to show a message login failed.
This is my code so far:
-(void)loginSucces
{
[self showLoginProcess:false];
PaMainViewController * vc = [[PaMainViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
-(void)loginFailed
{
//TODO: handle error
[self showLoginProcess:false];
NSLog(#"LoginVC Fail!");
}
i hope this is what u want..
Use UIAlertView if you just wants to notify the User that login is failed.
-(void)loginFailed
{
//TODO: handle error
[self showLoginProcess:false];
NSLog(#"LoginVC Fail!");
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Warning" message:#"LoginVC Fail!" delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
You can display UIAlertView with your message or you can use any third party toast notifications.
Best approach, when network request ends with some result, process that result and show it to user. Usually server provides some user readable information about reason of failure. Same story when for some reason server is not reachable, just take NSError and show user a localizedDescription.
Why this way. User will receive some useful information and you will have less headache with localization.
Good example: network is unavailable. In such case NSerror localizedDescription will provide nice explanation of the problem and user can do respective action like enable WiFi or go outside to restore 3g connection.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Order"
message:#"Order Successfully Discontinued."
delegate:self
cancelButtonTitle:nil
otherButtonTitles: #"Ok",nil];
//[alertView performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
alertView.tag=TAG_DEV;
[alertView show];
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
if(alertView.tag==TAG_DEV)
{
if(buttonIndex==0)
{
}
else
NSLog(#"Here");
}
}
This crashes. How can I fix it?
When you crash on a "objc_msgSend()" you are most likely sending a messageto an already-freed object. Or you have a pointer, which is correct, but something have changed the objects contents. Another cause can be the use of a dangling pointer that once pointed to the memory now occupied by your object. Occasionally objc_msgSend crashes because a memory error changed the runtime's own data structures, which then would cause trouble in the receiver object itself.
In this situation you need to check wheter or not, the delegate of the UIAlertview has been released after presenting, so when the alert is dismissed, it is not sending a message to it's delegate, which may be nil. An other option is that the UIViewController that presents the alert view is released after it is presented. Please check if the alert delegate is not released after it is presented.
When something goes so horribly wrong that my app can't continue and needs to exit, I want to pop up an alert box to the user, and then close the app when they tap the OK button. Sounds simple enough, right?
But here's the problem: my fatal error handler gets called by a 3rd party library (I don't have their source code). I give them a pointer to my fatal error handler on initialization, and when they encounter a fatal error they simply call that routine and expect it to never return. If it returns, the 3rd party library will assume I've handled the error and it will continue on its way (possibly corrupting data because things are now in an inconsistent state). I could just exit the application at the end of my error handler (which is what they expect), but I want to be able to display a message to the user first to tell them what the problem is.
Unfortunately, if I just do:
-(void)fatalErrorHandler:(NSString *)msg
{
// Log the error and shut down all the things that need
// to be shut down before we exit
// ...
// Show an alert to the user to tell them what went wrong
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:msg delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
exit(-1);
}
fatalErrorHandler returns right after [alert show], which tells the 3rd party library that I've handled the error and it will continue on as if nothing has happened. This is no good.
I need to NOT return from fatalErrorHandler. Ever. But since I'm on the main thread, the UIAlertView won't appear until fatalErrorHandler returns. A catch-22.
Any ideas on how I can show an alert to the user without returning from my fatal error handler?
I don't know if this would work, but what about starting a while loop with a sleep in its body for, let's say, 1 second each cycle? The while would exit when a Bool variable would have been set to YES, maybe from the alertViewDelegate.
From what you wrote "fatalErrorHandler returns right after [alert show], which tells the 3rd party library that I've handled the error and it will continue on as if nothing has happened."
I guess what you actually need is to pause everything when the fatalErrorHandler method is called. To achieve this, you can stop all NSTimer, queued methods etc. before displaying alertView.
Alternatively, you can display alertView via a different thread, and then use usleep(long long time) to pause the thread where fatalErrorHandler is in.
Okay, wesley6j's answer gave me an idea. Here's what I came up with:
-(void)fatalErrorHandler:(NSString *)msg
{
// Pop up an alert to tell the user what went wrong. Since this error
// handler could be called from any thread, we have to make sure this happens
// on the main thread because it does UI stuff
[self performSelectorOnMainThread:#selector(showMessage:) withObject:msg waitUntilDone:YES];
// Now, if we're NOT on the main thread, we can just sleep forever and never
// return from here. The error handler will exit the app after the user
// dismisses the alert box.
if (![NSThread isMainThread])
Sleep(0x7fffffff);
else
{
// OTOH, if we ARE on the main thread, we have to get a bit creative.
// We don't ever want to return from here, because this is a fatal error
// handler and returning means the caller can continue on its way as if
// we "handled" the error, which we didn't. But since we're on the main
// thread, we can't sleep or exit because then the user will never see
// the alert box we popped up in showMessage. So we loop forever and
// keep calling the main run loop directly to let it do its processing
// and show the alert. This is what the main run loop does anyway, so
// in effect, we just become the main run loop.
for (;;)
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
}
}
}
-(void)showMessage:(NSString *)msg
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:msg delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
[alert release];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
exit(-1);
}
This works perfectly and does exactly what I need.
Followup to Where is NSAlert.h in the iOS SDK?
Is there any way to get NSAlert runModal like behavior from a UIAlertView? Or from a UIActionSheet?
I'm planning on using only in debug builds so I'm not concerned with how it looks or if it uses undocumented functionality.
Edit:
NSAlert is part of the OS X SDK and is similar to MessageBox in Win32. It allows you to synchronously prompt the user for something. Here's an example:
NSAlert * myAlert=[[NSAlert alloc] init];
[myAlert setMessgeText:#"This is my alert"];
[myAlert addButtonWithTitle:#"button 1"];
[myAlert addButtonWithTitle:#"button 2"];
switch ([myAlert runModal]) {
case NSAlertFirstButtonReturn:
//handle first button
break;
case NSAlertSecondButtonReturn:
//handle second button
break;
}
runModal is a synchronous function, it shows the alert and waits for user response. Internally it is running a limited version of the message loop, but as far as the rest of my application is concerned, the world has stopped; no messages, no events, nothing.
Internally it is running a limited version of the message loop, but as far as the rest of my application is concerned, the world has stopped
Just do exactly what you described: throw up the alert, then run the event loop till the alert view gets dismissed. This code works:
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"O rlly?" message:nil delegate:nil
cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
NSRunLoop *rl = [NSRunLoop currentRunLoop];
NSDate *d;
while ([alert isVisible]) {
d = [[NSDate alloc] init];
[rl runUntilDate:d];
[d release];
}
[alert release];
You'll have to write your own if you want this behavior. Careful, if you block the main queue for too long, your app will be watchdog'd.
UIAlertView gives you modal behavior, and will end up working the same way your custom class will. You might consider using a block-based wrapper that wraps up UIAlertView and allows you to setup blocks for the button action callbacks.
I have a simple application which was working fine until I added some code which launches a new thread at some point and then tries to show an alert from that thread. Now, the app crashes whenever the code for showing the alert is hit.
UIAlertView * addAlert = [[UIAlertView alloc] initWithTitle:#"New alert"
message:#"Example alert"
delegate:nil
cancelButtonTitle:#"Cancel", otherButtonTitles:#"OK", nil];
[addAlert show];
[addAlert release];
My question is: is it possible to display UI elements such as alerts from multiple threads on iOS?
You definitely don't want to be displaying an alert (or anything UI-related) from any thread other than the main thread. I'd suggest putting your alert code in a function and call one of the performSelectorOnMainThread calls.
- (void) showAlert
{
UIAlertView * addAlert = [[UIAlertView alloc] initWithTitle:#"New alert"
message:#"Example alert"
delegate:nil
cancelButtonTitle:#"Cancel", otherButtonTitles:#"OK", nil];
[addAlert show];
[addAlert release];
}
// ... somewhere in the worker thread ...
[self performSelectorOnMainThread:#selector(showAlert) withObject:nil waitUntilDone:NO];
I'm pretty sure that the main thread is the only thread that should be the one that handles UI recognition/drawing things to screen. What I would do if I were in your position would to be either use KVO notifications or implement a protocol that some class subscribes to. Going the protocol route, when you get to the alerting part of your code merely have that thread call its protocol method, the subscribing class will be alerted by having the delegate function triggered and you can easily present whatever you have to in that view via the main thread.
Hope that helps.
Better and simple and just one line approach is to call performSelectorOnMainThread method with alertView.
In your case try this line
[addAlert performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
instead of
[addAlert show];
It will call show method of Alertview on main thread. No need to write any extra method.