I currently have a UIAlertController that I need to make non-dismissible. The Alert should not dismiss when pressing the action button.
How can I do this?
UIAlertController *alert;
int bestScore = [[[NSUserDefaults standardUserDefaults] objectForKey:#"bestScore"] intValue];
if (!bestScore || bestScore < _score){
[[NSUserDefaults standardUserDefaults] setObject:#(_score) forKey:#"bestScore"];
alert = [UIAlertController alertControllerWithTitle:#"GAME OVER "
message:[NSString stringWithFormat:#"NEW RECORD! \n SCORE : %d \n\n\n\n\n\n", _score] preferredStyle:UIAlertControllerStyleActionSheet];
}
else alert = [UIAlertController alertControllerWithTitle:#"GAME OVER"
message:[NSString stringWithFormat:#"SCORE : %d \n Best score : %d \n\n\n\n\n\n ", _score, bestScore] preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:#"Try again" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self newGame];
[self addNewView];
}]];
[alert addAction:[UIAlertAction actionWithTitle:#"Answer" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[alert viewWillDisappear:NO];
}]];
[self presentViewController:alert animated:YES completion:nil];
I would recommend creating a UIViewController that contains a UIView. Within this UIView you will be able to display your required information and add the custom button actions that you desire.
In order to make the UIView appear like a modal view controller add a UIVisualEffectView with a UIBlurEffectStyle.
This is as simple as creating a conventional view controller using your storyboard/xib adding the required UIView in the interface builder then linking up the associated view controller class. After completing the initial setup and user interface add the following code to viewDidLoad. Furthermore you can perform the required animations etc within viewWillAppear & viewWillDisappear.
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]
UIVisualEffectsView *blurView = [[UIVisualEffectView alloc] initWithEffect:effect];
[self.view insertSubview:blurView atIndex:0];
You can do like this
If you dont want dismiss alertController then You can disabled button actions of it like
alert.actions[1].enabled = NO
This will make your alertController Non-dismissable.
simply present the alertContoller again at the start of the action for alertButton you want it to not dismiss,
[self presentViewController:alertController animated:YES completion:nil];
Related
I am updating some UIAlertViews, deprecated since iOS 9.0 to UIAlertViewControllers.
With UIAlertView, it was possible to just throw an alert from any code being executed--even in a utility class or shared instance--with the simple line:
[alertView show];
So if I call a shared instance such as
- (void)didTapDeleteButton:(id)sender {
NSArray *itemsToDelete = [self.selectedIndexPathToContact allValues];
[[IDModel sharedInstance] deleteItems:itemsToDelete];
//which contains the code:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Keep Archive Copy?"
message:nil
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"OK",nil];
alertInvite.alertViewStyle = UIAlertViewStyleDefault;
alertInvite.tag=100;
[alertView show];
everything worked fine.
However, with the UIAlertController, this is not allowed. If you put the following code in the method of a class accessible via shared instance, when you get to presentViewController, it throws an error:
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:#"Delete Item?" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[alertView dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* noButton = [UIAlertAction actionWithTitle:#"Not Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[alertView dismissViewControllerAnimated:YES completion:nil];
}];
[alertView addAction:noButton];
[alertView addAction:yesButton];
if ([alertView respondsToSelector:#selector(setPreferredAction:)]) {
[alertView setPreferredAction:yesButton];
}
//FOLLOWING THROWS ERROR
[self presentViewController:alertView animated:YES completion:nil];
on the last line, that the class (reached via a shared instance) does not have this method. It seems you must use a more complicated way to throw alert. I've seen some somwehat convoluted approaches such as the following:
id rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
if([rootViewController isKindOfClass:[UINavigationController class]])
{
rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject;
}
if([rootViewController isKindOfClass:[UITabBarController class]])
{
rootViewController = ((UITabBarController *)rootViewController).selectedViewController;
}
[rootViewController presentViewController:alertInvite animated:YES completion:nil];
However, this does not work for me as I don't think my shared instance has a rootviewcontroller. Can anyone suggest a simple, straightforward way to do this?
I created an extension on UIViewController that allows me to create a new window and present a view controller from there. This allows me to present from any class, not just a view controller. Also, it prevents issues where you try to display an alert view from a view controller that is already presenting a view controller.
extension UIViewController {
func presentFromNewWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
let window = newWindow()
if let rootViewController = window.rootViewController {
window.makeKeyAndVisible()
rootViewController.present(self, animated: animated, completion: completion)
}
}
private func newWindow() -> UIWindow {
let window = UIWindow(frame: UIScreen.main.bounds)
let rootViewController = UIViewController()
rootViewController.view.backgroundColor = .clear
window.backgroundColor = .clear
window.rootViewController = rootViewController
window.windowLevel = UIWindowLevelAlert
return window
}
}
You can then use this method to present your Alert Controller (or any UIViewController):
alertViewController.presentFromNewWindow()
When you dismiss the alert view controller, it is removed from the view controller and the window is removed from the hierarchy.
to show an alert from any code I could think of doing:
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert" message:#"message" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction: [UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:nil]];
alert.TitleColor = [UIColor whiteColor];
id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate;
UIViewController *vc = delegate.window.rootViewController;
[vc presentViewController:alert animated:YES completion:nil];
Note:
Note that in most cases I would NOT do this.
Non ui code shouldn't do ui! Which is likely also part of the reason why apple made the change: It encourages a proper model||view separation
Your question is quite vague, I believe. But I think what you've been looking for is an example of presenting a UIAlertController. Is that correct? If so, continue reading...
Example:
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert" message:#"message" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction: [UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:nil]];
alert.TitleColor = [UIColor whiteColor];
[self presentViewController:alert animated:YES completion:nil];
doc: https://developer.apple.com/documentation/uikit/uialertcontroller
I am working on a project that uses UIAlertView, now the problem is I have to replace all of the UIAlertView's with UIAlertController's and there are around 1250 of them in the code. I am planning to uses the existing Utility class to create a function that does this, following is what I am planning to do:
+(void)showAlert:(NSString *)title errorMessage:(NSString *)msg {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
I have the following questions before doing this:
1 - Is this worth the efforts (Using UIAlertController instead of UIAlertView) ?
2 - How do I handle UIAlertView's that had tags and different delegate implementation across hundreds of files ?
3 - The fourth line the function above gives an error : No known class method for selector 'presentViewController:animated:completion:'
Any help is greatly appreciated.
You've to use UIAlertController as UIAlertView is deprecated.
Apple Doc says:
In apps that run in versions of iOS prior to iOS 8, use the
UIAlertView class to display an alert message to the user. An alert
view functions similar to but differs in appearance from an action
sheet (an instance of UIActionSheet). UIAlertView is deprecated in iOS
8. (Note that UIAlertViewDelegate is also deprecated.) To create and manage alerts in iOS 8 and later, instead use UIAlertController with a
preferredStyle of UIAlertControllerStyleAlert. Availability
Alternatively, you can do this way. In your Utils Class, do this:
+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)msg controller:(id)controller {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[controller presentViewController:alertController animated:YES completion:nil];
}
Usage from your ViewController class:
[Utils showAlertWithTitle:#"Camera" message:#"It seems that your device doesn't support camera. " controller: self];
If your existing UIAlertView is having tags then you've to use the UIAlertViewController class instead of the Util method.
Hope it helps.
You can use a common alertcontroller class method. Here is an example how you can solve the no.4 error. Basically you pass also the viewController that wants to call the alertview.
+(UIImage *)showAlert:(NSString *)title errorMessage:(NSString *)msg inViewController:(id)vc {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil]];
[vc presentViewController:alertController animated:YES completion:nil];
}
On the issue of alertviews with tags that require user input, to handle those inputs you might want to put the handler in and not let it nil and turn your method into a completionBlock method:
+(UIImage *)showAlert:(NSString *)title message:(NSString *)msg inViewController:(id)vc completedWithBtnStr:(void(^)(NSString* btnString))completedWithBtnStr {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
completedWithBtnStr(#"Yes");
}]];
[alertController addAction:[UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
completedWithBtnStr(#"No");
}]];
[vc presentViewController:alertController animated:YES completion:nil];
}
An example of calling this is
[YourClass showAlert:#"Sure?" message:#"Delete this record?" inViewController:self completedWithBtnStr:^(NSString* btnString) {
if ([btnString isEqualToString:#"Yes"]) {
// delete record
}
}];
The parameter btnString can be anything really. The code is not tested so if you found error, do inform me.
Answering your questions:
It's definitely worth doing that. You have to use UIAlertController anyway in near future as of UIAlertview is deprecated.
Use blocks or protocol to get a callback when delegate methods in utility class are fired.
Looks like you are presenting that on NSObject. Present on view class. In this case, you can use window rootViewController to present on
+(void) showAlert
{
UIWindow* topWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
topWindow.rootViewController = [UIViewController new];
topWindow.windowLevel = UIWindowLevelAlert + 1;
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"title" message:#"message" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Handle your yes please button action here
topWindow.hidden = YES;
//your ok button action handling
}];
[alertController addAction:okButton];
[topWindow makeKeyAndVisible];
[topWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}
I am adding the alertView in whole application. and i want to change the size of ok or cancel button in alertView so that i can set the small image of actions.
Anyone please help me for this.
You cannot change the frame of default Alert view for this you have to use custom alert view. Please refer the below links for custom alert views
https://github.com/dogo/SCLAlertView
https://github.com/vikmeup/SCLAlertView-Swift (Swift)
https://github.com/mtonio91/AMSmoothAlert
You can not change the size of ok or cancel button in alertView.
The only solution that I could figure is to make a custom view with UIVisualEffect and show it like UIActionSheet
Welcome if other solution is there :)
if you want to do this for adding image, then try following way:
UIAlertController * view= [UIAlertController
alertControllerWithTitle:#"Add Image"
message:#"Image Added successfully"
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* add = [UIAlertAction
actionWithTitle:#"add"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
//Do some thing here
[view dismissViewControllerAnimated:YES completion:nil];
}];
[add setValue:[[UIImage imageNamed:#"add.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forKey:#"image"];
[view addAction:add];
[self presentViewController:view animated:YES completion:nil];
I have a label which asks a questions and user needs to answer by tapping on "YES" or "NO" button. if user clicks directly on NEXT Button the alert pops up saying Choose Yes or No if user selects Yes then he gets a new sub question there itself , if user clicks on NEXT without answering the sub question another alertView should pop up. On both the Condition only one alert view is displayed. help plz
heres the code
-(IBAction)btnNextClicked:(id)sender {
activeButton = sender;
if (([_txtOwnerShipPercentage.text isEqualToString:#" "] || _txtOwnerShipPercentage.text==nil || _txtOwnerShipPercentage.text.length==0) && (activeButton.tag == 1)) {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Warning" message:#"Select Ownership Percentage" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
} else if (activeButton.tag == 0){
UIAlertController *alertController2 = [UIAlertController alertControllerWithTitle:#"Warning" message:#"Select 'YES' or 'No'" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController2 addAction:okAction];
[self presentViewController:alertController2 animated:YES completion:nil];
}
}
and i have set tags to YES and No Button... 1 & 2 respectively.
activebutton is nothing but globally declared UIButton
Here is the code of how you can manage two or multiple alert controllers in one context according to your requirement.
//declare first alert controller
UIAlertController *alertviewcontroller1 = [UIAlertController alertControllerWithTitle:#"My Alert 1"
message:#"This is an action sheet of alert 1"
preferredStyle:UIAlertControllerStyleActionSheet];
//declare second alert controller
UIAlertController *alertviewcontroller2 = [UIAlertController alertControllerWithTitle:#"My Alert 2"
message:#"This is an action sheet of alert 2"
preferredStyle:UIAlertControllerStyleActionSheet];
//declare first alert controller action with hendler
UIAlertAction *firstActionOfAlert1 = [UIAlertAction actionWithTitle:#"YES"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
//Present second alert controller when user taps on 'YES' of first alert controller.
[self presentViewController:alertviewcontroller2 animated:YES completion:nil];
}];
//declare second alert controller action with hendler
UIAlertAction *firstActionOfAlert2 = [UIAlertAction actionWithTitle:#"YES"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
}];
//add both actions relatively
[alertviewcontroller1 addAction:firstActionOfAlert1];
[alertviewcontroller1 addAction:firstActionOfAlert2];
//Present first alert controller
[self presentViewController:alertviewcontroller1 animated:YES completion:nil];
the above solution will not work in case if previous alert is not fully presented or without tapping an action in alertView. Use this method for that,
if (self.presentedViewController & !self.presentedViewController.isBeingPresented) {
[self.presentedViewController presentViewController:alert animated:YES completion:nil];
}
I am trying to understand a weird behavior of my app, here is a description (tested in a trivial project).
ViewControllerA is presenting modally ViewControllerB
ViewControllerB contains a button, this button is presenting a UIAlertController specified this way
alert = [UIAlertController alertControllerWithTitle:#"Test" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:#"Action" style:UIAlertActionStyleDefault handler:^(UIAlertAction *handler) { NSLog(#"Action"); }]];
The ViewControllerB is presenting alert this way
- (IBAction)button:(id)sender {
alert.popoverPresentationController.sourceView = self.button;
alert.popoverPresentationController.sourceRect = self.button.bounds;
[self presentViewController:alert animated:YES completion:nil];
}
Now, if you click on the button, the alert appears, if you click outside the alert, the alert disappears (I am on iPad). You can do it as many times as you want...
Here is the bug: When the alert is presented, if you click twice outside (quickly enough, ~0,2s interval), the alert disappears AND ViewControllerB is dismissed. At the end we can see ViewControllerA but we never asked for it.
There is also a warning message:
Warning: Attempt to dismiss from view controller <UIViewController: 0x7f85ab633f70> while a presentation or dismiss is in progress!
Thank you for your help.
I would prefer to add a UITapGestureRecognizer at the end, on your UIAlertController. like :
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Test"
message:#"Test Message."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *closeAction = [UIAlertAction actionWithTitle:#"Close"
style:UIAlertActionStyleDefault
handler:nil];
UIAlertAction *someAction = [UIAlertAction actionWithTitle:#"Action"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
....
}];
[alertController addAction:closeAction];
[alertController addAction:someAction];
[self presentViewController:alertController animated:YES completion:^{
[alertController.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action:nil]];
}];