I have a few ViewController subclasses inside a UINavigation controller. I have successfully used UIAlertViews elsewhere in the application, and I know how to set the delegate and include the correct delegate methods, etc.
In a ViewController with a UITableView, I have implemented a 'pull to refresh' with a UIRefreshControl. I have a separate class to manage the downloading and parsing of some XML data, and in the event of a connection error, I post a notification. The view controller containing the table view observes this notification and runs a method where I build and display an alert:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Connection Error" message:[[notification userInfo] objectForKey:#"error"] delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
alertView.alertViewStyle = UIAlertViewStyleDefault;
[alertView show];
The alert displays correctly, but the cancelButton is unresponsive - there is no way to dismiss the alert! Putting similar code (identical, but without the notification's userinfo) in the VC's viewDidLoad method creates an alert that behaves normally.
Is the refresh gesture hogging first responder or something? I have tried [alertView becomeFirstResponder]. I would be grateful for any advice…
Update: screenshot included… is this the right info? (can't embed this image for lack of reputation) http://i.stack.imgur.com/4CGqS.png
Edit
It seems like you have a deadlock or your thread is stuck waiting. You should look at your code and see what causes this.
Original answer which lead to update in OP
Make sure the alert is shown on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
//Open alert here
});
This isn't a solution per se, but I might try two quick things as you troubleshoot:
1) Hardcode some text in the UIAlert, rather than passing in the notification object. See if there is any change in behavior.
2) Try adding another button to the alert and an accompanying method to catch it. So you'll see if the delegate is getting ny buttons messages at all.
Try adding a tag to the alertView
alertView.tag = 0;
Then create the method alertView:clickedButtonAtIndex: in the view controller.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (alertView.tag == 0) {
[alertView dismissWithClickedButtonIndex:0 animated:YES];
}
}
Related
In an app that I'm building using SocketRocket is a websockets client and when there is an alert or error the server sends an appropriate message to the app.
All the sockets communication code, for example the important:
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {[...] }
exist within the root viewcontroller of the app.
All the message processing is happening at another class and the user navigates freely through the app.
When I want to show though a simple alert with the message received from the server (either UIAlertView or UIAlertController) and an "OK" button (to just dismiss it), as soon as the user clicks the "OK" button they are returned to the first (root) view controller which in my case is a login credentials screen.
At present the code firing the alerts is within the didReceiveMessage in the root controller.
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
syncDevices *syncDev = [syncDevices new];
NSArray *devArr;
devArr = [syncDev parseDeviceXMLData:data];
if (devArr.count > 0) {
if ([devArr[0] isEqual: #"error"]) {
// error message receved, display an alert!
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Warning!"
message:devArr[1]
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[errorAlert show];
return;
}
}
}
Is it possible for the user to dismiss the alert AND stay on the same screen as they were? How do I do that?
I want to avoid if possible having to do a func in every screen to display the alert...
Thanks!
The code you have shown has nothing related to navigation between different views, as far as I can see. Pressing OK button will change views (push or pop) only, if you have implemented some UIAlertViewDelegate method and there manually do it.
Suggestion: change delegate:self to delegate:nil to disable all possible side-effects. Now pressing OK button should just remove the popup and nothing else.
I have a UIAlertView that I display with no buttons. I'd like to programmatically dismiss it after some action is processed (it is a "Please wait" alert dialog). I'd like to dismiss it, though, without the need for the UIAlertView to be a property.
Why: Now I am allocating the alert view to a #property - e.g.: I am creating a class variable. I don't feel like it deserves to have it that way - because frankly, it is displayed only when the View Controller is loading. I thought it is somehow added as a subview, that I could pop it from the stack when the loading is done, but that didn't work.
What: I create the alert dialog (no buttons) and show it. Then I start processing data - syncing with server. It only happens once and it is not a frequent thing. However, other object takes care of the sync and is implemented as observer pattern - the object itself reports, when the data has been loaded. That's when I dismiss the dialog. I just wanted to avoid using #property for the dialog.
This is how I do it (simplified):
#property (nonatomic, strong) UIAlertView *av;
- (void)setup {
...
[self.av show];
[self loadData];
}
- (void)loadData {
...loading data...
[self.av dismissWithClickedButtonIndex:0 animated:YES];
}
Is there a way how to dismiss it without the need for "storing" it to #property?
Blocks retain variables they capture. You can take advantage of that behaviour, but you should understand what you're doing there:
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Title"
message:#"Message"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:nil];
[av show];
dispatch_async(dispatch_queue_create("com.mycompany.myqueue", 0), ^{
sleep(5);
dispatch_async(dispatch_get_main_queue(), ^{
[av dismissWithClickedButtonIndex:0 animated:YES];
});
});
The sleep(5) is just simulating your long running task.
Instead of using UIAlertView, I'd consider using a library like this: https://github.com/jdg/MBProgressHUD
I've got an issue where an object that's creating a UIAlertView is sending a message back to its delegate, but the delegate object is crashing.
I've got it working OK in a couple of other instances, but what I'm trying to do in this case (and what's different from other similar questions) is that the object that instantiates the alert, and which acts as its delegate is not itself a view, but rather an object that is instantiated within a view. To wit, in the parent view:
#implementation
*MyCustomObject customObject;
-(void)viewDidLoad {
customObject = [[MyCustomObject alloc] init];
}
#end
And in the custom object:
-(void)DoCoolThings {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Do You Want To Do Cool Things"
message:#"...description of cool things..."
delegate:self
cancelButtonTitle:#"No Thanks"
otherButtonTitles:#"HELLS YES", nil];
[message show];
}
and
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
[self DoCoolThings];
} else {
[self GoAndCry];
}
}
When I do the alert view within a viewcontroller object, everything is fine. Doing it within this sub-object which itself has no view allows me to create the alert, but that object--which shouldn't be getting de-allocated based on the scoping--doesn't seem to want to continue to act as a delegate.
The error I'm getting is indeed a de-allocation message, but I feel strongly that this is not the problem because if I remove the alert, all the other stuff--specifically, it's a wrapper for a storekit purchase process--works fine, and all those delegate methods work happily.
I've got a solution which will allow me to move the Alert into the parent view's methods, but I was hoping not to have to. Is this limitation real, or is it my imagination? IE am I doing something else wrong?
I display an alert view in the didConnect method for bluetooth. For some reason, it fires four times. I'm trying to bandaid it and it is not working too well. Basically, I put the alert view in a method of its own, and call that method in didConnect. That's when it fires four times. I'm trying to set it up to only fire once. What I tried to do was set the alert views method to return a TRUE value. Then I do this:
if ([successfulConnection self] == FALSE) {
[self successfullConnection];
}
This works great the first time, but then the method is set to TRUE for the remainder of the time. I have the feeling that if I set it back to equal FALSE at the end of the if statement, then it will fire four times and I'll be right back where I started. Does anyone know how to change the above code to have it only fire once when it tries to fire four times?
Also tried replacing the above code with this in my didConnect, but it never fired at all:
[successfulConnection self];
if (successfulConnection.visible == YES) {
[successfulConnection dismissWithClickedButtonIndex:0 animated:YES];
}
If you call successfulConnection from your didConnect method, I think this should work (myAlert is the property name for the alert view):
-(void)successfulConnection {
if (! self.myAlert) {
self.myAlert = [[UIAlertView alloc]initWithTitle:#"ttile" message:#"message" delegate:self cancelButtonTitle:#"cancel" otherButtonTitles: nil];
[self.myAlert show];
}
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
self.myAlert = nil;
//do whatever with the result
}
The simplest thing to do is to just have a boolean that gets set to true when the UIAlertView is displayed, and then false when the UIAlertView is dismissed. Then, whenever you are going to show the UIAlertView, first check if it is already displaying.
These are methods you can use according to your requirement:
EDIT : Perfect way... if you dont want to upload your app on App Store
To know that alertView is currently visible or not.
Usage : Display alertView only if necesary other its already present.
-(UIAlertView *)getLastAlertView
{
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
return topMostAlert;
}
Dissmiss any alertView present which you don't know.
Usage : dissmiss all alertView and then present new one
-(void)dissmissLastAlert
{
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
if (topMostAlert) {
[topMostAlert dismissWithClickedButtonIndex:0 animated:YES];
}
}
I have this code
- (IBAction)save:(id)sender {
self.imageView.image = [UIImage imageNamed:#"loading.gif"];
[self.view setUserInteractionEnabled:NO];
MBProgressHUD *hudSave = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hudSave.labelText = #"Saving...";
NSLog(#"save");
UIAlertView *deco;
if (!isVideo) {
UIImageWriteToSavedPhotosAlbum (imageView.image, nil, nil , nil);
deco = [[UIAlertView alloc]initWithTitle:#"Save" message:#"Your photo has been saved." delegate: nil cancelButtonTitle:#"oK" otherButtonTitles:nil];
}else{
//UIAlertView *explain;
//explain = [[UIAlertView alloc]initWithTitle:#"Wait during processing" message:#"Your video is being filtered, this process may be long, depending of both video and device. Please do not close this app until task is finished." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
UIAlertView *explain;
explain = [[UIAlertView alloc]initWithTitle:#"Wait during processing" message:#"Your video is being filtered, this process may be long, depending of both video and device. Please do not close this app until task is finished." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[explain show];
[self InitialisationRefiltrage:urlVideo];
//[self InitialisationRefiltrage:urlVideo];
deco = [[UIAlertView alloc]initWithTitle:#"Save" message:#"Your video has been saved." delegate: nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
}
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
[deco show];
[self.view setUserInteractionEnabled:YES];
}
The problem part is if my file is a video, it goes directly too "initialisationRefiltrage" (who is working fine) but without displaying the MBProgressHUD and the alert view explain, and after my video traitement, it display everything (explain, deco, and the MBProgressHUD) at the same time.
I try something with dispatch, thread, etc... But i think a don't do it correctly, so can you please give me a clue too how to do that.
Have a nice day.
Put the code [self InitialisationRefiltrage:urlVideo] in the delegate method of your UIAlertView so that it is executed only when the alert has been displayed and user has tapped on a button of the alert.
You may also use instead some third-party UIAlertView subclasses that uses completion blocks to make your code only execute when the alert is dismissed. See my class that does this for example.
Besides, you should respect coding conventions and use a method name beginning with a lowercase letter to make your code more readable.
The UI is updated in the "run loop".
The calls you're making tell iOS to display some views (alert, MUD...) and it'll do that on the next run through the loop.
What you need to do is wait for the user to respond to the alert before continuing. You do this by setting yourself as the UIAlert's delegate, then responding to the event:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
...
}
There are also libraries available that allow you to pass a block to the alert view, thus simplifying the whole thing. (https://github.com/jivadevoe/UIAlertView-Blocks, for example)
P.S. I see that you're new to Stack Overflow - please tick my answer if you're happy that it has responded to your question...