iOS/Objective-C: Call AlertViewController from sharedInstance - ios

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

Related

IOS/Objective-C: Launch UIAlertController from code in subclassed Class in Storyboard app

Two View Controllers in my app subclass the same class that has common methods. I would like to launch an alertController (fire an alert) from code in a method of this common sub-classed controller. But the following code is not launching anything.
Can anyone suggest the right way to point to the subclassed VC to get the alertController to launch?
Thanks in advance for any suggestions.
View controller1 wired to storyboard subclasses common class as follows:
#interface IDManageItemsVC : IDCommonVC <UIAlertViewDelegate>
The common VC subclasses CoreVC which has even more common methods for the whole app:
#import "IDCoreVC.h"
#interface IDCommonVC : IDCoreVC<UITableViewDelegate,UITableViewDataSource,UIAlertViewDelegate>
I am trying to fire the alert from code in commonVC (the superclass for the class wired to storyboard) as follows:
-(void)fireAlert {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Delete?" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
//run code
}];
UIAlertAction* noButton = [UIAlertAction
actionWithTitle:#"Not Now"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
//if chosen run code
}];
[alert addAction:noButton];
[alert addAction:yesButton];
if ([alert respondsToSelector:#selector(setPreferredAction:)]) {
[alert setPreferredAction:yesButton];
}
/* following points to VC not in hierarchy so commented out
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]; */
//Following does not do anything
[self presentViewController:alert animated:YES completion:nil];
}
Edit:
Using the following method, with a breakpoint, I visually verified that topViewController is the right one and then presented the alertview from it and it still did not display. The only thing I noticed is that when I visually examined the alertview, it appears blank with just a slight white curve in the upper left where a rounded corner might be against a white rectangle. So perhaps, there is something wrong with the way I'm creating the alertview.
UIViewController *currentTopVC = [self currentTopViewController];
currentTopVC.presentViewController.........
- (UIViewController *)currentTopViewController {
UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
while (topVC.presentedViewController) {
topVC = topVC.presentedViewController;
}
return topVC;
}
and instead of [self presentViewController..., the following:
UIViewController *currentTopVC = [self currentTopViewController];
[currentTopVC presentViewController:alertInvite animated:YES completion:nil];
I copied and pasted your code and replaced
[self presentViewController:alertInvite animated:YES completion:nil];
with
[self presentViewController:alert animated:YES completion:nil];
and it worked.
This doesn't look like a inheritance issue to me. Are you by any chance calling this method from viewDidLoad: or any method that gets called before the view controller is actually shown? If so, try calling it from viewDidAppear:

Show UIAlertController from a common utility class

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];
}

Dont dismiss UIAlertController

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];

UIAlertView show() behavior for UIAlertController

In previous versions of iOS I was able to call show on a UIAlertView in the App Delegate. More specifically, show was called in:
func applicationDidBecomeActive(application: UIApplication)
Since UIAlertViews disregarded the view hierarchy in most cases, this solution worked no matter where the user was in the app.
With the introduction of UIAlertController this problem becomes a little trickier. UIAlertController is now a subclass of UIViewController and needs to be presented just like any other UIViewController. While presenting the UIAlertController from the keyWindow's rootViewController works, it's not the ideal solution.
Does anyone have any ideas on replicating [UIAlertView show] functionality for a UIAlertController? Any way to show the UIAlertController on app active without traversing the view hierarchy?
I figured out a solution that I believe to be more elegant than the answer I posted previously. I'll copy and paste the answer I posted to a similar question. Follow the link at the bottom of my post if you just want to see the code.
The solution is to use an additional UIWindow.
When you want to display your UIAlertController:
Make your window the key and visible window (window.makeKeyAndVisible())
Just use a plain UIViewController instance as the rootViewController of the new window. (window.rootViewController = UIViewController())
Present your UIAlertController on your window's rootViewController
A couple things to note:
Your UIWindow must be strongly referenced. If it's not strongly referenced it will never appear (because it is released). I recommend using a property, but I've also had success with an associated object.
To ensure that the window appears above everything else (including system UIAlertControllers), I set the windowLevel. (window.windowLevel = UIWindowLevelAlert + 1)
Lastly, I have a completed implementation if you just want to look at that.
https://github.com/dbettermann/DBAlertController
Here's what I ended up with:
public class func visibleViewController() -> UIViewController? {
return self.visibleViewController(UIApplication.sharedApplication().keyWindow?.rootViewController?)
}
private class func visibleViewController(viewController: UIViewController?) -> UIViewController? {
if viewController?.presentedViewController == nil {
println("Visible view controller: \(viewController)")
return viewController
} else if let navigationController = viewController as? UINavigationController {
return self.visibleViewController(navigationController.topViewController)
} else if let tabBarController = viewController as? UITabBarController {
return self.visibleViewController(tabBarController.selectedViewController)
} else {
return self.visibleViewController(viewController?.presentedViewController)
}
}
Try This
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:#"title"
message:#" Your message hear"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okBtnAction = [UIAlertAction actionWithTitle:#"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
//Do what action u want.
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:okAction];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
[alert dismissViewControllerAnimated:YES completion:nil];
//do something when click button
}];
[alert addAction:Action];
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
[vc presentViewController:alert animated:YES completion:nil];
Try using JSAlertView which handles both UIAlertView and UIAlertController APIs.
It provides short and easy methods for displaying alerts and handles multiple alerts fired at same time, very well.

UIAlertController dismissing his presentingViewController

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]];
}];

Resources