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
Related
So I have two different UIAlertViews in the same view controller and both alerts can be triggered at the same time. When both alerts are triggered, both alerts pop up at the same time, with the alerts being layered on top of each other. Is there a way to stagger the alerts so that when the first alert comes up, the second alert will not pop up until the user dismisses the first alert? For my code, this is the format I'm using
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"ERROR!"
message:#"Error message here!"
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
try the following:
create two properties
#property (weak, nonatomic) UIAlertView *visibleAlertView;
#property (strong, nonatomic) UIAlertView *pendingAlertView;
every time when you want to present an alertview from your code make a check
UIAlertView *newAlertView = [[UIAlertView alloc] init...
if (self.visibleAlertView) {
self.pendingAlertView = newAlertView;
} else {
self.visibleAlertView = newAlertView;
[newAlertView show];
}
and finally:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (self.pendingAlertView) {
UIAlertView *newAlertView = self.pendingAlertView;
self.pendingAlertView = nil;
self.visibleAlertView = newAlertView;
[newAlertView show];
}
}
hope that helps :)
EDIT
you could even stack the pending alertviews:
#property (strong, nonatomic) NSMutableArray *pendingAlertViews;
...
self.pendingAlertViews = [NSMutableArray array];
before presenting an alertview:
UIAlertView *newAlertView = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Message" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
if (self.visibleAlertView) {
[self.pendingAlertViews addObject:newAlertView];
} else {
self.visibleAlertView = newAlertView;
[newAlertView show];
}
and in dismiss:
if (self.pendingAlertViews.count > 0) {
UIAlertView *av = self.pendingAlertViews.firstObject;
[self.pendingAlertViews removeObjectAtIndex:0];
self.visibleAlertView = av;
[av show];
}
hope it helps :)
Why don't you make a class level variable that indicates an alertView is open. Then before you open one you check that variable and if it's set you don't pop up the second one. Instead you could have it set another variable that indicates the second box should pop up. Then in the - alertView:clickedButtonAtIndex: method you can pop up the second one if the second variable is set.
I think I am pretty late for this but still posting as It might be useful for someone looking for this.
I have created a AQAlertAction subclass for UIAlertAction. You can use it for staggering Alerts, the usage is same as you are using UIAlertAction. All you need to do is import AQMutiAlertFramework in your project or you can include class also (Please refer Sample project for that). Internally It uses binary semaphore for staggering the Alerts until user handle action associated with current alert displayed. Let me know if it works for you.
I'm creating a wrapper for UIAlertView (I know about UIAlertController and about several already existing wrappers, it's also for educational purposes).
Suppose it looks like this (very shortened version):
#interface MYAlertView : NSObject
-(void)show;
#end
#interface MYAlertView()<UIAlertViewDelegate>
#end
#implementation MYAlertView
-(void)show {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Some title"
message:#"Some message"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil];
[alertView show]
}
#pragma mark UIAlertViewDelegate implementation
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
//Do something.
}
#end
And, for instance, I use it like this:
// USAGE (inside some ViewController)
-(void)showMyAlert {
dispatch_async(dispatch_get_main_queue(), ^{
MYAlertView *myAlertView = [[MYAlertView alloc] init];
[myAlertView show];
});
}
The problem I have is the following:
[myAlertView show] causes the alertView to appear. myAlertView is set as a delegate of the alertView.
There is the only strong reference to myAlertView: inside the block in the showMyAlert method. When it's finished, myAlertView is deallocated.
When the user clicks a button on the alertView, the alertView calls it's delegate method, but the delegate (myAlertView) is deallocated already, so it causes BAD_ACCESS (the delegate in UIAlertView is declared as assign, not weak).
I want to make MYAlertView as easy to use as it is with UIAlertView, so I don't want to make the user store a strong reference to it somewhere (it is inconvenient).
So, I have to keep the myAlertView alive as long as the alertView is shown somehow. The problem is I can't think of any way other than creating a strong reference inside MyAlertView, assigning it to self, when I show the alertView, and assigning it to nil, when I dismiss it.
Like so (only the changed bits):
#interface MYAlertView()<UIAlertViewDelegate>
//ADDED:
#property (nonatomic, strong) id strongSelfReference;
#end
#implementation MYAlertView
-(void)show {
UIAlertView *alertView = [[UIAlertView alloc] init /*shortened*/];
[alertView show]
//ADDED:
self.strongSelfReference = self;
}
#pragma mark UIAlertViewDelegate implementation
//ADDED:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
self.strongSelfReference = nil;
}
#end
It should work: the moment the alertView is dismissed, the strongSelfReference will be set to nil, there will be no strong references left to the myAlertView, and it will get deallocated (in theory).
But keeping a strong reference to self like this looks evil to me. Is there a better way?
UPDATE: The MYAlertView in reality is an abstraction layer around the now deprecated UIAlertView and a new UIAlertController (iOS 8+), so subclassing UIAlertView is not an option.
Yes, your object should keep a strong reference to itself. It's not evil to do so.
A self-reference (or, in general, any reference cycle) is not inherently evil. The evil comes in creating one unintentionally such that it is never broken, and thus leaks objects. You're not doing that.
I feel like the answer here is to actually implement MYAlertView as a subclass of UIAlertView instead of an object that floats in the ether. It will stick around as long as your internal UIAlertView would have regularly stuck around.
#interface MYAlertView : UIAlertView
#end
#implementation MYAlertView
- (instancetype)init {
if (self = [super initWithTitle:#"Some title"
message:#"Some message"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil]) {
// Other setup?
}
return self;
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Respond.
}
#end
Update: You should instead create an iOS7 analogy for UIAlertViewController.
#interface MyAlertViewController : UIViewController <UIAlertViewDelegate>
+ (id)makeMeOne;
#end
#implementation MyAlertViewController
- (void)viewDidAppear:(BOOL)animated {
UIAlertView *alert = [[UIAlertView alloc] init..];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Respond.
[self.navigationController popViewControllerAnimated:NO];
}
+ (id)makeMeOne {
if (iOS7) {
return [[self alloc] init];
} else {
return [[UIAlertViewController alloc] init];
}
}
#end
Fill in the blanks for setup.
In my opinion, this is an indicator of a bad design.
If you are creating a wrapper for both iOS version's you would be better off exposing a delegate for MYAlertView that mirrors the relevant dismiss actions (or else you won't be able to act on the callbacks and further than this wrapper).
If you are keeping a strong reference to yourself without adding any actual value to the class you are wrapping, maybe it would be better to write a wrapper that accepts a block to callback on completion? At least this way you can monitor the user's actions and let the caller dictate how long the alert is relevant.
After all, if you pass on the delegation then the problem is solved in a readable manner?
I have a class that I instance to show an alert view like this:
- (void)showAlert
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Do you want to try again?"
message:nil
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
[alertView show];
}
}
I need self to be the delegate because I need alertView:didDismissWithButtonIndex: to be called to perform some actions when the user taps the alert view's button. This usually works well, but from time to time, I get this crash:
SIGSEGV
UIKit-[UIAlertView(Private) modalItem:shouldDismissForButtonAtIndex:]
I guess this is because the delegate, for any reason, was released, right? Or is this because what was released was the alert view? How could I solve this? I need the alert view to have a delegate, and I've reading several related posts and I couldn't find an answer that fits my scenario.
I'm testing in iOS 7.0, I don`t know if that could have to do with the issue.
Thanks in advance
It seems that you tap alert when its delegate is released:
delegate:self
It happens because UIAlertView delegate property is of assign type (not weak!).
So your delegate potentially can point to released object.
Solution:
in dealloc method you need to clear delegate for your alertView
- (void)dealloc
{
_alertView.delegate = nil;
}
But before you need to make iVar _alertView and use it for your alertViews
- (void)showAlert
{
_alertView = ...;
[_alertView show];
}
Update your code as follows:
- (void)showAlert {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Do you want to try again?"
message:nil
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
[alertView show];
}
Its due to you are missing the nil for otherButtonTitles part.
Missing sentinel in method dispatch warning will be shown if you didn't add nil.
That's because the alertView's delegate object released while clicking the button. I think it's a bug of SDK:
#property(nonatomic,assign) id /*<UIAlertViewDelegate>*/ delegate; // weak reference
should be:
#property(nonatomic, weak) id /*<UIAlertViewDelegate>*/ delegate; // weak reference
To fix the issue:
add a weak delegate for UIAlertView using association.
swizzle the init, setDelegate: delegate methods, set alertView delegate to self, set step 1 weak delegate with the param delegate.
implement all delegate methods, deliver the methods using the weak delegate.
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];
}
}
I have a viewController class A that have a method creating a UIAlertView and implement UIAlertView Delegate method, and a NSObject model class B for processing logging and networking. In class B, just allocating an A class instance, then call the method of A. The alert view was displayed normally, but when i clicked "Ok" button, it's just crashed. I want click "Ok" button to reopen keyboard let user continue login after failured. (Had declared UIAlertView protocol in header file.)
In viewcontroller class A:
- (void)displayAlertViewString:(NSString *)string
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Login Failured!"
message:string
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"Ok", nil];
[alert show];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if ([title isEqualToString:#"Ok"])
{
//reopen the keyboard let user continue login.
[self.passwordField becomeFirstResponder[;
}
And in model class B, i called display alert view method in the failure block of AFNetworking.
failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(#"%#", error);
RDLoginViewController *loginViewController = [[RDLoginViewController alloc] init];
[loginViewController displayAlertViewString:#"The entered email or password was incorrectly!"];
There is no any information in debugger, Xcode just stuck on the thread view. Can anyone help me figure it out? Thanks.
After the failure block execution, your loginViewController is released from memory, since nobody has a strong reference to it.
When the alertView tries to access it's delegate it crashes, since its delegate is not in the memory anymore.
I'd recommend you to take a look at the Advanced Memory Management Programming Guide.