Issue
I'm having issues with UIAlertController. It's an iPad project, in that I have two classes, say First and Second.
I'm presenting Second from First, using a modal presentation. In Second Class I have a button, clicking on that I'm showing UIActionSheet to perform some actions. It works perfectly except if user clicks on the button rapidly the Second class is being dismissed.
Code
First VC:
- (IBAction)showNext:(id)sender
{
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondVC"];
[self presentViewController:vc animated:YES completion:nil];
}
Second VC:
- (IBAction)showActionSheet:(UIButton *)sender
{
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:nil
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[alertVC addAction:[UIAlertAction actionWithTitle:#"LogOut"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action)
{
}]];
[alertVC addAction:[UIAlertAction actionWithTitle:#"Update"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
}]];
[alertVC setModalPresentationStyle:UIModalPresentationPopover];
UIPopoverPresentationController *popPresenter = [alertVC popoverPresentationController];
popPresenter.sourceView = sender;
popPresenter.sourceRect = sender.bounds;
[self presentViewController:alertVC animated:YES completion:nil];
}
No other code in both classes. I don't know why it happens, Actually it happened to one of my Client project, So I created a test app with above code and it's also causing the issue.
Screen Capture
What I have tried:
I checked UIAlertController Class Reference
Did a search on Google,, nothing came up
Alternatives:
If I set Second class as my rootViewController, the issue won't be there (But I can't do that, I need to navigate back and forth)
If I disable the popover dismissal when touched outside, it can avoid the issue. But I need this workflow.
Can anybody please help me ? I'm totally lost at this moment, not getting any useful info.
Thanks in advance.
I don't know what is the issue, but I fixed it. I used popoverPresentationControllerShouldDismissPopover and returned YES.
- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController;
{
return YES;
}
Eureka:
It was an accident!!! I didn't get any other way to fix this issue, so I thought will do the second alternative. While writing I accidentally wrote YES instead of NO. But it worked, I don't know how it worked and what is the reason for that issue.
Related
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:
I've got a problem with understanding, how the iOS view controllers and alert controllers work in a specific case:
I have a custom UINavigationController in which there is my UIViewController. My Navigation controller has overridden dismissViewControllerAnimated:completion method. From this UIViewController I present new UIAlertController. Up to the point where the user clicks any button in the Alert, everything works fine. However, the strange part is, my custom UINavigationController's dismissViewControllerAnimated:completion method is being called (I don't want that, if possible...)
The Alert is presented in a regular manner (from the UIViewController within the UINavigationController):
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"yep" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self takeOrder:data];
}];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"nope" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}];
[confirmOrderAcceptAlert addAction:okAction];
[confirmOrderAcceptAlert addAction:cancelAction];
[self presentViewController:alert animated:YES completion:nil];
Is there any option to prevent this behavior? Why does this happen in the first place?
EDIT:
The code for dismissViewControllerAnimated:completion:
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
self.isHeroEnabled = NO;
[super dismissViewControllerAnimated:flag completion:completion];
}
I'm using Hero library to animate transitions, could this be the case?
As you are subclassing UINavigationController, it is will definitely call dismissViewControllerAnimated:completion.
To avoid it from disturbing the library code, check for specific ViewController Types.
Eg:
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
if(![self.visibleViewController isKindOfClass:[UIAlertController class]]){
self.isHeroEnabled = NO;
}
[super dismissViewControllerAnimated:flag completion:completion];
}
That's how the UINavigationController works
If you don't want to set that HeroEnabled for the actions called due to alerts. You might have to do something like
if(![self.visibleViewController isKindOfClass:[UIAlertController class]]) {
self.isHeroEnabled = NO
}
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];
During development of my iOS application, I would like to be able to show the user a dialog if a serious bug occurs.
on SO, found that this could be achieved with adding something like the following to my AppDelegate class
- (void) notifyUserAboutSeriousBugWithMessage:(NSString *)message {
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Serious bug: Alert developer"
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"Continue" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self.window.rootViewController presentViewController:alert animated:YES completion:^{}];
}
However, when I run this, i get an error message saying I'm Attempting to present <A> on <B> whose view is not in the window hierarchy!
My application is using a storyboard and the Entry/Start point <B> is a Splash Screen (-viewController). [This is not contained in a NavigationController]
When I'm done with the splash screen, I use a Modal Segue to show "the rest" of the app.
So how can I workaround this? In my app delegate, I want to get some view controller that will be in the view hierarcy and show my Alert in it's view.
(The reason I want to put this in the app delegate is that I want to present error alerts from classes that are not view controllers)
Thanks!
That's probably because you already have a presented viewController on top of your rootViewController. You need to present your alert on top of the topmost view controller.
Here is how you can get the topmost controller :
UIViewController *topViewController = self.window.rootViewController;
while (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
}
You can then do :
[topViewController presentViewController:alert animated:YES completion:nil];
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.