My tabbar application has three tabs, each with its own navigational structure. Several of the views throughout my app load data via web service calls. These views register this notification in order to know when the app becomes active, so that they can reload the data:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadView) name:UIApplicationDidBecomeActiveNotification
object:NO];
When the app becomes active, these views all try to reload their data; however, if there is no Internet connection, the errors are caught and a UIAlert is shown to the user. The problem is that if 3 of these views are trying to reload data, 3 alert messages pop up.
How do I prevent multiple alerts from popping up to the user?
I appreciate all your thoughts, ideas, and suggestions!!
Thanks!
Brad
Edit: I tried putting this in my appDelegate, but even using this method, I seem to get multiple popups.
-(void)displayAlertWithTitle:(NSString *)title message:(NSString *)message delegate:(id)delegate{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:delegate cancelButtonTitle:#"Cancel" otherButtonTitles:#"Retry",nil];
[alert show];
[alert release];
}
Keep track of whether a alert is currently being displayed (or has been dismissed recently). There really isn't another way.
If you stick the functionality in your app delegate, then you can just do something like [(MyAppDelegate*)[UIApplication sharedApplication].delegate displayNetworkFailureDialog].
EDIT: Note that some people may frown on sticking random global cruft in your app delegate...
Related
I'm in the process of submitting my app to the App Store, but I read that I must notify the user if the internet connection is down when my app needs it. The Apple page mentioned Reachability as well. Currently, though, I'm using the UIWebView delegate method didFailLoadWithError...
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error Loading" message:[error localizedDescription] delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
...and it's working fine. My question is, will my app be rejected for not using Reachability to do this, or is it fine doing what I am currently doing?
Thanks in advance.
No, you're perfectly ok using didFailLoadWithError:.
Reachability class could be used to check if host is up (or internet connection at all) before even trying to load some page. But it is not neccessery, as long as you handle the possible errors - which obviously you do.
EDIT:
It is still a good practice to know wheather you will be able to reach a certain host or not. You could even modify GUI for each case (instead of just reporting an error). But this can always be done in update :)
I hope to access UIAlertView using tag. The codes show below
UIAlertView *a=(UIAlertView *)[self.view viewWithTag:presetTag];
but a returns no object(0x0)
I am looking to find a way to get the pointer to the UIAlertView object that is displayed without creating a reference to it in my UIViewController class that is displaying it. I am creating the UIAlertView and assigning it's tag property a constant non-zero value, then displaying it via show and releasing the UIAlertView reference.
An example where this could come in handy is if I want to hide the alert view based on some other event that is not touching one of the buttons on the alert view. Say a server informs the app that the alert is no longer valid and so I dismiss with button index -1 on the alert view. But, I have no reference to that alert so how can I find it?
Welcome any comment
Thanks
interdev
As the UIAlertView is not part of the application's view hierarchy, the only way to access it would be to store the instance right after you created it, such as in a dictionary so you can later retrieve it.
Something like:
UIStateAlertView *alert = [[UIStateAlertView alloc]
initWithTitle:#"my_message"
message:nil
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles: nil];
alert.tag = kMY_TAG;
[_alertIndex setObject:alert forKey:[NSNumber numberWithInt:kMy_TAG]];
[alert show];
[alert release];
to retrieve the alert:
UIAlertView *alert = [_alertIndex objectForKey:[NSNumber numberWithInt:kMy_TAG]];
When you're done with the view, make sure to remove it from the dictionary:
[_alertIndex removeObjectForKey:[NSNumber numberWithInt:kMy_TAG]];
_alertIndex being a NSMutableDictionary
iPhone SDK: check if a UIAlertView is showing provides a solution, however this is relying on how Apple does its internal affair and could break at any moment; and so should be avoided
UIAlertView creates its own UIWindow, which is not part of your app's main hierarchy of UIViews. Therefore it is not possible to find it using [UIView viewWithTag:].
You must store the pointer to the UIAlertViews you create in order to find them again later.
There is a way to access UIAlertViews in your app (and then you could use tags to verify that it's the one you're looking for), but it is relying on the internal structure of the app and may therefore stop working in future versions of iOS, though it is unlikely. If you are interested, please see this other SO response.
I think it is possible, please check that you are setting a non-zero value to the tag.
Is this what you need? Specifically speaking, you can access each UIAlertView's tag info through its tag property in protocol callback methods.
#protocol UIAlertViewDelegate <NSObject>
#optional
// Called when a button is clicked. The view will be automatically dismissed after this call returns
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
// Called when we cancel a view (eg. the user clicks the Home button). This is not called when the user clicks the cancel button.
// If not defined in the delegate, we simulate a click in the cancel button
- (void)alertViewCancel:(UIAlertView *)alertView;
- (void)willPresentAlertView:(UIAlertView *)alertView; // before animation and showing view
- (void)didPresentAlertView:(UIAlertView *)alertView; // after animation
- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex; // before animation and hiding view
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex; // after animation
#end
I am working on an app where a UIAlert pops up the first time the user begins using it to ask if they want to use their current location. This happens within the main controller. However, I get the following error when using the UIAlert:
wait_fences: failed to receive reply: 10004003
I debugged it, and googled around. I am not using a textarea or keyboard input, so I don't have to resign a first responder. However, I still cannot figure out why I would be getting this error. It only appears when I add the UIAlert.
MainController.m
- (void)viewDidLoad {
UIAlertView *mainAlert = [[UIAlertView alloc]
initWithTitle:#"Location"
message:#"Do you wish to use your current location?"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"No Thanks", #"Sure!", nil];
[mainAlert show];
[mainAlert release];
[super viewDidLoad];
}
The header file is constructed like this:
#interface MainController : UIViewController
<CLLocationManagerDelegate,
MKReverseGeocoderDelegate, UIAlertViewDelegate>
The reason for this wait_fences: failed to receive reply: can also be that you try to show an alert while not being on the mainThread (which is the GUI thread).
Dispatching to the GUI thread helps:
dispatch_async(dispatch_get_main_queue(), ^{
[mainAlert show];
});
I solved this problem by adding a slight delay to the UIAlert: The below is within my ViewDidLoad method (it also works fine within ViewDidAppear):
[self performSelector:#selector(testAlert) withObject:nil afterDelay:0.1];
for some reason that delay did the trick. I then called another method (I will rename it of course..):
- (void) testAlert
{
UIAlertView *mainAlert = [[UIAlertView alloc] initWithTitle:#"Location" message:#"Do you wish to use your current location?" delegate:self cancelButtonTitle:nil otherButtonTitles:#"No Thanks", #"Sure!", nil];
[mainAlert show];
[mainAlert release];
}
Always call [super viewDidLoad]; before you do anything else. That is also explaining why the delay works.
Hope I'm right. :)
Try showing the UIAlert in -viewDidAppear:(BOOL)animated. -viewDidLoad is called when your view is loaded, but before it is on screen.
I had a similar problem. I was adding an observer in the ViewDidLoad for a reachability notification, which was getting posted before the ViewDidAppear was finished.
So the ViewController was trying to display one UIAlertView before its own View was drawn completely.
I moved the adding of Observers to the ViewDidAppear, and started the startNotifier of reachability in the ViewDidAppear. Also the call to [super viewDidAppear] was missed from the code. Both these issues corrected, the app became working fine again.
If you get this wait_fences: failed to receive reply: 10004003 error once, you may not be able to present any modal ViewControllers in the app further. This was the problem I had in my app. Sometimes when trying to present one modal ViewController, the app would go into an infinite loop.
As Alan said, always remember to "Try showing the UIAlert in -viewDidAppear:(BOOL)animated. -viewDidLoad is called when your view is loaded, but before it is on screen."
I have a working application
before the first view is loaded, i put an alert in the viewWillAppear method:
- (void)viewWillAppear
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"MyAppp" message:#"Application will connect to Internet. Continue?"
delegate:self cancelButtonTitle:nil otherButtonTitles:#"No, quit", #"Yes", nil];
[alert show];
[alert release];
}
I can get the clicks on the two button (Yes/No) correctly...
But...I would like code execution to stop and wait for an answer, but instead the code goes on, connects to the internet and retrieves data...
How do I prevent a view to load, based on a user input?
The viewWillAppear is a notification which allows you to complete some stuff before the view is shown, you can't avoid the appearing of the view here. You have to review your implementation.
Just break your one viewWillAppear method into two methods. Don't try to do it all in one chunk of sequential code.
The first method will launch the alert and then just exit/quit/return.
The second method can be called by the alert button response handler, and then finish loading the view only after it's been called by the alert handler, after the user has responded.
You may or may not have to save extra state information (in extra properties or instance variables instead of method locals) between the first and second methods.
the below can be used, i know its little old question but might be useful for others
..
[alert show];
while ((!alert.hidden) && (alert.superview != nil))
{
[[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];
}
I solved it by showing the answer in the "ViewDidLoad", got a delegate to get which button was pressed and then processing the data ONLY if the user pressed "Yes"
I write a piece of code to "do something->show alert1->do something->show alert2".
//do something
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Alert 1"
message:nil
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
//do something
UIAlertView *alert2 = [[UIAlertView alloc]
initWithTitle:#"Alert 2"
message:nil
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert2 show];
[alert2 release];
And suddenly a strange thing happened to multiple AlertViews: It shows "Alert 1"->"Alert 2"(Press 'OK')->"Alert 1". Why "Alert 1" shows again? I haven't written any delegate method yet. Maybe a bug?(Thanks to cobbal, alert1 is still there when alert2 appears.)
I find -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex works well. Is the delegate method a common way to show multiple alertViews?
I would guess that alert 1 is shown and then covered by alert 2 since show isn't modal. When alert 2 is closed, alert 1 is still open.
To your second question, alertView:didDismissWithButtonIndex: may work better, but I haven't actually tested that.
The delegate is so that you can be notified when the alert is dismissed, and which button was used to dismiss it. It doesn't impact whether the alert is dismissed at all.
The alert will remain visible until it is dismissed either by you tapping a button (if any - they are not required) or you call either [UIAlertView dismissWithClickedButtonIndex:animated] or the (undocumented) dismiss method of the alert instance.
It looks like (as Cobbal suggested), alert 2 is popping up over alert 1, you dismiss alert 2, and alert 1 remains there (until it itself is dismissed).
Is there a particular reason you want to show a new alert while another is still showing? Perhaps some more context would help us to get to the root of the issue, which I suspect may be a design issue.
[edit] coming back to this and reading again, I wonder if what you are asking about with the delegate method is whether you should be showing alert 2 from there? In which case that's probably what you want - whether directly or indirectly. By indirectly I mean that you may have some state set elsewhere that determines whether alert 2 should be shown (or the circumstances that lead to it). That state (a flag, perhaps) could be set when you show the first alert, and cleared when the alert is dismissed (from the delegate method).
The reason this happens is because UIAlertView doesn't block while it's showing. Any code written after showing an alertview will run straight after the alert is shown.
What you should have is two different methods. One that does something and then shows an alert, and then another that does something and shows another alert.
Kick off the first method to do something and show an alert, and then hook into the alert's delegate method, and when you get the callback from the alertview, run the other method.
This way, the second part of the process won't happen until the user has pressed OK on the alert in first part of the process.