Can an AlertView be timed out in an iOS app? - ios

I want to know if it is possible for an AlertView to time out if it has been visible on the screen for a certain period of time without receiving any acknowledgement from the user, and if so, how? Is there a way for the AlertView object be linked with an NSTimer object?
My basic AlertView code is the following:
- (IBAction)showMessage:(id)sender {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Hello World!"
message:#"This is your first UIAlertview message."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
}

This is how I implemented in one of my apps
Inside the #interface declare your objects so you can keep track of them and add the if required
#property (nonatomic, strong) UIAlertView *myAlert;
#property (nonatomic, weak) NSTimer *myTimer;
In your code where you need to launch the alert add the following
self.myAlert = [[UIAlertView alloc]initWithTitle:#"TEST" message:#"TEST" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(cancelAlert) userInfo:nil repeats:NO];
[self.myAlert show];
Somewhere in your code add next function to dismiss the alert and invalidate the NSTimer
- (void)cancelAlert {
[self.myAlert dismissWithClickedButtonIndex:-1 animated:YES];
}
Also remember to invalidate the timer if a button is touched.
- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex {
[self.myTimer invalidate];
// Process pressed button
}
It may need some tweaks for your requirements.

Yes. Use dismissWithClickedButtonIndex:animated:
For example with a dispatch_after block, like this:
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[message dismissWithClickedButtonIndex:message.cancelButtonIndex animated:YES];
});
If you want to use a NSTimer just save the UIAlertView in an instance variable so you can access it from within the timer method.

You could create a category for UIAlertView and add an observer which listens out and if it is triggered, removes itself:
#implementation UIAlertView (Cancellable)
+ (instancetype)cancellableAlertViewWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:nil];
if (otherButtonTitles != nil)
{
va_list args;
va_start(args, otherButtonTitles);
for (NSString *buttonTitle = otherButtonTitles; buttonTitle != nil; buttonTitle = va_arg(args, NSString*))
{
[alertView addButtonWithTitle:buttonTitle];
}
va_end(args);
}
[[NSNotificationCenter defaultCenter] addObserver:alertView selector:#selector(removeAlertView:) name:#"AlertsShouldBeCancelledNotification" object:nil];
return alertView;
}
- (void)removeAlertView:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self dismissWithClickedButtonIndex:-1 animated:YES];
}
#end
Then you could create an NSTimer in your main class and have it trigger the notification when the selector is called.

Use an NSTimer to call dismissWithClickedButtonIndex:animated: and invalidate it if the user clicks in time. Using dispatch_after risks sending the message to a released instance if the user has dismissed it already.

Take a look at this answer: dismissing a UIAlertView programmatically . Using performSelector:withObject:afterDelay: is much more elegant than building and tearing down a timer, in my opinion.

Related

UIAlertView button action not working

I have one alert view and when I click on yes button it is supposed to produce another alert view and a toast message,but it is not happening. I couldn't figure it out. Here is my code:
-(void)myMethod {
UIAlertView *saveAlert = [[UIAlertView alloc] initWithTitle:#"First Message"
message:#"My First message"
delegate:nil
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
saveAlert.tag=0;
[saveAlert performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:NO];
}
This is the method I am using to provide the functionality for different alert views.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(alertView.tag==0) {
if (buttonIndex == 0)
{
//Code for Cancel button
}
if (buttonIndex == 1)
{
//code for yes button
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.mode = MBProgressHUDModeText;
hud.labelText = #"Successfully displayed First Message";
hud.margin = 10.f;
hud.yOffset = 150.f;
hud.removeFromSuperViewOnHide = YES;
[hud hide:YES afterDelay:3];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Second Message"
message:#"My second message"
delegate:nil
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes",nil];
alert.tag=1;
[alert performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
}
}
if (alertView.tag==1) {
if (buttonIndex == 0)
{
//Code for Cancel button
}
if (buttonIndex == 1)
{
//Code for yes Button
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.mode = MBProgressHUDModeText;
hud.labelText = #"Succesfully displayed Second Message";
hud.margin = 10.f;
hud.yOffset = 150.f;
hud.removeFromSuperViewOnHide = YES;
[hud hide:YES afterDelay:3];
}
}
}
Can anyone help in finding the issue. Why I cannot get my second alert after clicking yes button in first alert?
You have not set the delegate for your UIAlertView and also make sure your delegate conforms to UIAlertViewDelegate protocol. Find the code snippet below.
You controller conforms to UIAlertViewDelegate protocol:
#interface YourViewController : UIViewController <UIAlertViewDelegate>
Create UIAlertView and set the deleagte:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"First Message"
message:#"Show second message"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alertView show];
Implement UIAlertViewDelegate delegate method:
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if( 0 == buttonIndex ){ //cancel button
[alertView dismissWithClickedButtonIndex:buttonIndex animated:YES];
} else if ( 1 == buttonIndex ){
[alertView dismissWithClickedButtonIndex:buttonIndex animated:YES];
UIAlertView * secondAlertView = [[UIAlertView alloc] initWithTitle:#"Second Message"
message:#"Displaying second message"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[secondAlertView show];
}
}
You are specifying nil as the delegate for your alert views. You need to specify an object so the alertView:clickedButtonAtIndex: method can be called!
If you'd like to handle this more clear, you could use a block-based AlertView.
Create new file->Subclass of->UIAlertView
SuperAlertView.h
#import <UIKit/UIKit.h>
#class MySuperAlertView;
typedef void (^MySuperAlertViewBlock) (MySuperAlertView *alertView);
#interface MySuperAlertView : UIAlertView
- (instancetype) initWithTitle:(NSString *)title message:(NSString *)message;
- (void) addButtonWithTitle:(NSString *)buttonTitle block:(MySuperAlertViewBlock) block;
#end
SuperAlertView.m
#import "MySuperAlertView.h"
#interface MySuperAlertView()<UIAlertViewDelegate>
#property NSMutableArray *blocks;
#end
#implementation MySuperAlertView
- (instancetype)initWithTitle:(NSString *)title message:(NSString *)message
{
if (self = [super initWithTitle:title message:message delegate:self cancelButtonTitle:nil otherButtonTitles:nil])
{
self.blocks = [NSMutableArray array];
}
return self;
}
- (void)addButtonWithTitle:(NSString *)buttonTitle block:(MySuperAlertViewBlock)block
{
[self addButtonWithTitle:buttonTitle];
[self.blocks addObject:block ? [block copy] : [NSNull null]];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
MySuperAlertViewBlock block = self.blocks[buttonIndex];
if ((id) block != [NSNull null]){
block(self);
}
}
#end
Usage:
MySuperAlertView *alertView = [[MySuperAlertView alloc] initWithTitle:#"Info" message:NSLocalizedString(#"EMAIL_SENT_SUCCESSFULL", nil)];
[alertView addButtonWithTitle:#"Ok" block:^(MySupertAlertView *alertView) {
// handle result from ok button here
}];
[alertView addButtonWithTitle:#"cancel" block:NULL];
dispatch_async(dispatch_get_main_queue(), ^{
[alertView show];
});

Change UIAlertView message

I have a problem with UIAlertView message, I need to change alert message with loop
First,
I declare this in implementation
NSString *alertMessage;
UIAlertView *theAlert;
and
- (IBAction)doSearching:(id)sender
{
theAlart = [[UIAlertView alloc] initWithTitle:nil message:#"Searching..."
delegate:self cancelButtonTitle:nil otherButtonTitles:nil, nil];
[theAlart show];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:#selector(searchAfterAlertShow) userInfo:nil repeats:NO];
}
- (void)searchAfterAlertShow
{
for (int i = 0; i < [dataArray count]; i++) {
alertMessage = [NSString stringWithFormat:#"Searching... (%d/%d)"
, i, [dataArray count]];
NSLog(#"%#", alertMessage);
// do something here
if (i == [dataArray count]) {
[theAlart dismissWithClickedButtonIndex:0 animated:YES];
}
}
}
and in Alert delegate
- (void)didPresentAlertView:(UIAlertView *)alertView
{
[theAlart setMessage: alertMessage];
}
The Log is writing:
Searching... (1/8)
Searching... (2/8)
...
Searching... (8/8)
but the alert message changed at the end of loop
If the operations are too fast, the dismiss animation will take longer than the loop itself.
Most UI animations are async, therefore they happen in the background and they don't wait. you can either delay the operation to compensate for the animation duration, or pass NO for animated parameter in dismissWithClickedButtonIndex:animated

xcode UIAlertView on an NSTimer

I want to put an alert view on a 15 minute timer with a YES or NO button. This works fine if the user stays on that view. However the UIAlertView uses a local variable for its title and in the delegate method. When the user changes views the program crashes. Can I make a UIAlertView wait for 15 minutes then implement the delegate method? I tried to put the delegate method in other views, but don't know how to pass the variable with the alert. I've done a little research and think maybe with a Notification or background thread (but background threads I don't think can do UI stuff and an alert is UI)
- (IBAction)sendInAppMsg:(UIButton *)sender
{
....
//****This is the message that calls the UIAlertView on a timer
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(pause) userInfo:nil repeats:NO];
}
-(void) pause
{
UIAlertView *responseAlert = [[UIAlertView alloc] initWithTitle:#"Success?" message:[NSString stringWithFormat:#"Did you reach %# %#", self.currentDoc.firstName, self.currentDoc.lastName ] delegate:self cancelButtonTitle:nil otherButtonTitles:#"Yes", #"No", nil];
[responseAlert show];
}
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//Update Parse Cloud with users response
PFObject *contactAttempt = [PFObject objectWithClassName:#"ContactAttempt"];
contactAttempt [#"inApp"] = #"push";
contactAttempt [#"from"] = [[PFUser currentUser] username];
contactAttempt [#"to"] = [NSString stringWithFormat:#"%# %#", self.currentDoc.firstName, self.currentDoc.lastName ];
if (buttonIndex == 0) {
contactAttempt [#"response5"] = #YES;
[contactAttempt saveInBackground];
}
if (buttonIndex == 1)
{
contactAttempt [#"response5"] = #NO;
[contactAttempt saveInBackground];
}
}
//works fine and updates cloud with user response unless user changes views :(

NSOperaionQueue and UIAlertView

The problem is that if I create and display two alert - the second will override the first, and after it closed displayed first. So not pretty.
I'm trying to create a queue alerts with NSOperationQueue. That you could add a few alerts and they show a sequence to close. But I can not do so would be that I add operations are performed sequentially, waiting for the previous one. They are executed in parallel.
AlertOperation.h
#import <Foundation/Foundation.h>
#interface AlertOperation : NSOperation<UIAlertViewDelegate>
#property (nonatomic,assign) BOOL isFinishedAlert;
- (AlertOperation *)initWithAlert:(UIAlertView *)alert;
#end
AlertOperation.m
#import "AlertOperation.h"
#interface AlertOperation()
{
UIAlertView *_alert;
}
#end
#implementation AlertOperation
#synthesize isFinishedAlert = _isFinishedAlert;
- (AlertOperation *)initWithAlert:(UIAlertView *)alert
{
self = [super init];
if (self)
{
_alert = alert;
_alert.delegate = self;
[_alert show];
}
return self;
}
- (void) main
{
_isFinishedAlert = NO;
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!_isFinishedAlert);
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
_isFinishedAlert = YES;
}
- (BOOL) isConcurrent
{
return NO;
}
#end
Here is run code
UIAlertView *u1 = [[UIAlertView alloc] initWithTitle:#""
message:#"Hello i am first alert" delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
UIAlertView *u2 = [[UIAlertView alloc] initWithTitle:#""
message:#"Hello i am second alert" delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
NSOperation *alertOp1 = [[AlertOperation alloc] initWithAlert:u1];
NSOperation *alertOp2 = [[AlertOperation alloc] initWithAlert:u2];
alertsQueue = [[NSOperationQueue alloc] init];
[alertsQueue setMaxConcurrentOperationCount:1];
[alertsQueue addOperation:alertOp1];
[alertsQueue addOperation:alertOp2];
Make this easier on yourself. Create a mutable array. When you have new alerts to show then push them onto the array. Every time an alert finishes (gets its delegate message), then dispatch the next alert onto the main queue:
NSMutableArray *alerts;
... end of Alert Delegate message
if([alert count]) {
UIAlert *alrt = [alerts objectAtIndex:0];
[alerts removeObjectAtIndex:0];
dispatch_async(dispatch_get_main_queue(), ^{ [alrt show]; } );
}
I moved the [_alert show] to -(void)main method and it worked! Thank you, #phix23 for help!

UIAlertview exc_bad_access [duplicate]

This question already has answers here:
UIAlertView fails to show and results in “EXC_BAD_ACCESS” error
(6 answers)
Closed 8 years ago.
I add a function to dismiss the UIAlertView after several seconds.The whole code is like:
- (void)netWorkAlert
{
UIAlertView *netWork = [[UIAlertView alloc] initWithTitle:#"error" message:#"network has problems" delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[netWork show];
[self performSelector:#selector(dismissAlert:) withObject:netWork afterDelay:2];
}
- (void)dismissAlert:(UIAlertView *)alert
{
if(alert)
{
[alert dismissWithClickedButtonIndex:0 animated:YES];
[alert release];
}
}
the netWorkAlert is invoked when the network is unavailable.
Now the problem I met is when the netWorkAlert is invoked at the second time, the app is broken and the Xcode shows error in
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([ZJAppDelegate class]));
//Thread 1 :EXC_BAD_ACCESS(code=1,address=xc0000004)
}
}
I didn;t use ARC and I don't know why it crashes. Even I comment the [alert release];, it still has the same problem at the second time.
Could anyone help me to check it?
thanks!
The EXC_BAD_ACCESS is caused by accessing a released object. To avoid this make your call to UIAlertView kind of modal:
Function body:
-(void)checkSaving
{
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Do you want to add these results to your database?"
message:#"\n\n"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Save", nil];
alert.alertViewStyle = UIAlertViewStyleDefault;
[alert show];
//this prevent the ARC to clean up :
NSRunLoop *rl = [NSRunLoop currentRunLoop];
NSDate *d;
d= (NSDate*)[d init];
while ([alert isVisible]) {
[rl runUntilDate:d];
}
}
Your choice result:
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
// the user clicked one of the OK/Cancel buttons
if (buttonIndex == 1)//Save
{
//do something
}
if (buttonIndex == 0)//NO
{
//do something
}
}
Register the functions in the interface declaration:
#interface yourViewController ()
-(void)checkSaving
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
//...
#end
To call:
[self checkSaving];
I wish this will help you.
The UIAlertView could be out of scope by the time the dismissAlert method is called (your checking for alert being nil will prevent this code crashing. There is, however, a better way of implementing this where alert will never be out of scope.
Your class that defines the networkAlert method should implement the <UIAlertViewDelegate> protocol. The code below allows you to intercept the user clicking the 'cancel' button and perform a custom action. The default action of pressing cancel is to close the UIAlertView.
#interface YourClassName : UIViewController <UIAlertViewDelegate> {}
#implementation YourClassName
-(void) networkAlert
{
UIAlertView *netWork = [[UIAlertView alloc] initWithTitle:#"error"
message:#"network has problems"
delegate:self
cancelButtonTitle:#"cancel"
otherButtonTitles:nil];
[netWork show];
}
- (void) alertViewCancel:(UIAlertView*)alertView
{
what ever it is you want to do when the cancel button is pressed here
}

Resources