Is it possible to rotate UIAlertController (and Alert) in landscape orientation? - ios

I am editing an app where I added a new UIViewController to configure a multiplayer game in one layout.
I have added two buttons for each player (top button, bot button). Each button generates an alert at the screen (remember it is in landscape orientation ) but the problem is, player 2 do not see the alert rotated, I mean, the alert is supposed to be shown in his orientation, in front of me.
As there any way to do this? I want to rotate an Alert for the player 2 will not see the information of the alert turned.
Here the alert code I have:
#IBAction func ShowAlert(){
let message = "Test"
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: .Default, handler: nil)
alert.addAction(action)
presentViewController(alert, animated: true, completion: nil)
}

Here is a way to rotate an alert controller view, although it is not perfect. The controller tries to present based on the orientation and I found that attempting to transform it before it is presented is unsuccessful.
In order to not see "incorrect" orientation initially, you must set the alert to hidden and then make it visible once it is "presented".
let alert // setup
alert.view.hidden = true;
[self presentViewController:c animated:YES completion:^{
alert.view.transform = CGAffineTransformMakeRotation(M_PI);
alert.view.hidden = false;
}];
The only outstanding issue I have with this is that after the user interacts with the alert, it flashes back to the initial transformation (orientation dependent). I currently have not found a solution for this, but will update if I ever do.
Alternatively, using a custom "alert" would solve this problem as you would have more control over its view. This is probably the more reliable approach.

Here is my solution.
presentViewController with no animation to avoid flash when present the alert controller.
[self presentViewController:alert animated:NO completion:^{
[alertController.view setTransform: CGAffineTransformMakeRotation(M_PI_2)];
}];
then create a UIAlertController category named e.g. UIAlertController+Orientation,
hide the view in viewWillDisappear to avoid flash back when dismiss the alert controller.
#import "UIAlertController+Orientation.h"
#implementation UIAlertController (Orientation)
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.view setHidden:YES];
}
- (BOOL) shouldAutorotate {
return NO;
}
#end

Related

inputAccessoryView animating down when alertController (actionSheet) presented

I have an inputAccessoryView for a chat app that always remains visible and docked at the bottom of the screen for text input similar to most messaging apps.
When I present an alertController with actionSheet style, the inputAccessoryView animates down off screen as the alert is presented and then back up again when the alert is dismissed. This in turn scrolls my tableView and is undesirable.
This is happening because the chat viewController is giving up firstResponder when the alert is presented.
Is there anyway to present an alertController and not give up firstResponder, or anyway to keep an inputAccessoryView docked at the bottom of the screen when it's view resignsFirstResponder?
The InputAccessoryView sits outside of your ViewController's hierarchy - it's contained in the UITextEffectsWindow whose rootViewController is a UIInputWindowController. Similarly the keyboard is contained in UIRemoteKeyboardWindow and its own UIInputWindowController.
So, if we present the alert from the topmost window or higher (UITextEffectsWindow or UIRemoteKeyboardWindow), it won't resign first responder.
The simplest solution I've found is:
let topViewController = UIApplication.shared.windows.last!.rootViewController!
topViewController.present(alert, animated: true, completion: nil)
Ideally you would safely handle those optionals. A potentially better solution (I've seen some console errors from the previous solution) would be to create a new UIWindow with a higher WindowLevel, make it the key window and visible, and present the alert from there.
Thanks to #Corey W. (give votes to him) we have a solution for this issue. Safer way in Swift 5:
// Instantiate AlertController
let actionSheet = UIAlertController(title: title,
message: "Comment options",
preferredStyle: .actionSheet)
// Add actions
actionSheet.addAction(UIAlertAction(title: "Edit",
style: .default,
handler: { (_: UIAlertAction) in
self.showEditingView(withCommentId: commentId, withCommentText: commentText)
}))
// Present AlertController
if let lastApplicationWindow = UIApplication.shared.windows.last,
let rootViewController = lastApplicationWindow.rootViewController {
rootViewController.present(actionSheet, animated: true, completion: nil)
}
For those looking the Objective-C equivalent of Corey's answer:
UIViewController *objViewController = [UIApplication sharedApplication].windows.lastObject.rootViewController;
[objViewController presentViewController:view animated:YES completion:nil];

UIAlertController does not display [duplicate]

Just started using Xcode 4.5 and I got this error in the console:
Warning: Attempt to present < finishViewController: 0x1e56e0a0 > on < ViewController: 0x1ec3e000> whose view is not in the window hierarchy!
The view is still being presented and everything in the app is working fine. Is this something new in iOS 6?
This is the code I'm using to change between views:
UIStoryboard *storyboard = self.storyboard;
finishViewController *finished =
[storyboard instantiateViewControllerWithIdentifier:#"finishViewController"];
[self presentViewController:finished animated:NO completion:NULL];
Where are you calling this method from? I had an issue where I was attempting to present a modal view controller within the viewDidLoad method. The solution for me was to move this call to the viewDidAppear: method.
My presumption is that the view controller's view is not in the window's view hierarchy at the point that it has been loaded (when the viewDidLoad message is sent), but it is in the window hierarchy after it has been presented (when the viewDidAppear: message is sent).
Caution
If you do make a call to presentViewController:animated:completion: in the viewDidAppear: you may run into an issue whereby the modal view controller is always being presented whenever the view controller's view appears (which makes sense!) and so the modal view controller being presented will never go away...
Maybe this isn't the best place to present the modal view controller, or perhaps some additional state needs to be kept which allows the presenting view controller to decide whether or not it should present the modal view controller immediately.
Another potential cause:
I had this issue when I was accidentally presenting the same view controller twice. (Once with performSegueWithIdentifer:sender: which was called when the button was pressed, and a second time with a segue connected directly to the button).
Effectively, two segues were firing at the same time, and I got the error: Attempt to present X on Y whose view is not in the window hierarchy!
viewWillLayoutSubviews and viewDidLayoutSubviews (iOS 5.0+) can be used for this purpose. They are called earlier than viewDidAppear.
For Display any subview to main view,Please use following code
UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (yourCurrentViewController.presentedViewController)
{
yourCurrentViewController = yourCurrentViewController.presentedViewController;
}
[yourCurrentViewController presentViewController:composeViewController animated:YES completion:nil];
For Dismiss any subview from main view,Please use following code
UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (yourCurrentViewController.presentedViewController)
{
yourCurrentViewController = yourCurrentViewController.presentedViewController;
}
[yourCurrentViewController dismissViewControllerAnimated:YES completion:nil];
I also encountered this problem when I tried to present a UIViewController in viewDidLoad. James Bedford's answer worked, but my app showed the background first for 1 or 2 seconds.
After some research, I've found a way to solve this using the addChildViewController.
- (void)viewDidLoad
{
...
[self.view addSubview: navigationViewController.view];
[self addChildViewController: navigationViewController];
...
}
Probably, like me, you have a wrong root viewController
I want to display a ViewController in a non-UIViewController context,
So I can't use such code:
[self presentViewController:]
So, I get a UIViewController:
[[[[UIApplication sharedApplication] delegate] window] rootViewController]
For some reason (logical bug), the rootViewController is something other than expected (a normal UIViewController). Then I correct the bug, replacing rootViewController with a UINavigationController, and the problem is gone.
Swift 5 - Background Thread
If an alert controller is executed on a background thread then the "Attempt to present ... whose view is not in the window hierarchy" error may occur.
So this:
present(alert, animated: true, completion: nil)
Was fixed with this:
DispatchQueue.main.async { [weak self] in
self?.present(alert, animated: true, completion: nil)
}
TL;DR You can only have 1 rootViewController and its the most recently presented one. So don't try having a viewcontroller present another viewcontroller when it's already presented one that hasn't been dismissed.
After doing some of my own testing I've come to a conclusion.
If you have a rootViewController that you want to present everything then you can run into this problem.
Here is my rootController code (open is my shortcut for presenting a viewcontroller from the root).
func open(controller:UIViewController)
{
if (Context.ROOTWINDOW.rootViewController == nil)
{
Context.ROOTWINDOW.rootViewController = ROOT_VIEW_CONTROLLER
Context.ROOTWINDOW.makeKeyAndVisible()
}
ROOT_VIEW_CONTROLLER.presentViewController(controller, animated: true, completion: {})
}
If I call open twice in a row (regardless of time elapsed), this will work just fine on the first open, but NOT on the second open. The second open attempt will result in the error above.
However if I close the most recently presented view then call open, it works just fine when I call open again (on another viewcontroller).
func close(controller:UIViewController)
{
ROOT_VIEW_CONTROLLER.dismissViewControllerAnimated(true, completion: nil)
}
What I have concluded is that the rootViewController of only the MOST-RECENT-CALL is on the view Hierarchy (even if you didn't dismiss it or remove a view). I tried playing with all the loader calls (viewDidLoad, viewDidAppear, and doing delayed dispatch calls) and I have found that the only way I could get it to work is ONLY calling present from the top most view controller.
I had similar issue on Swift 4.2 but my view was not presented from the view cycle. I found that I had multiple segue to be presented at same time. So I used dispatchAsyncAfter.
func updateView() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
// for programmatically presenting view controller
// present(viewController, animated: true, completion: nil)
//For Story board segue. you will also have to setup prepare segue for this to work.
self?.performSegue(withIdentifier: "Identifier", sender: nil)
}
}
My issue was I was performing the segue in UIApplicationDelegate's didFinishLaunchingWithOptions method before I called makeKeyAndVisible() on the window.
In my situation, I was not able to put mine in a class override. So, here is what I got:
let viewController = self // I had viewController passed in as a function,
// but otherwise you can do this
// Present the view controller
let currentViewController = UIApplication.shared.keyWindow?.rootViewController
currentViewController?.dismiss(animated: true, completion: nil)
if viewController.presentedViewController == nil {
currentViewController?.present(alert, animated: true, completion: nil)
} else {
viewController.present(alert, animated: true, completion: nil)
}
You can call your segues or present, push codes inside this block:
override func viewDidLoad() {
super.viewDidLoad()
OperationQueue.main.addOperation {
// push or present the page inside this block
}
}
I had the same problem. I had to embed a navigation controller and present the controller through it. Below is the sample code.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImagePickerController *cameraView = [[UIImagePickerController alloc]init];
[cameraView setSourceType:UIImagePickerControllerSourceTypeCamera];
[cameraView setShowsCameraControls:NO];
UIView *cameraOverlay = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 768, 1024)];
UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"someImage"]];
[imageView setFrame:CGRectMake(0, 0, 768, 1024)];
[cameraOverlay addSubview:imageView];
[cameraView setCameraOverlayView:imageView];
[self.navigationController presentViewController:cameraView animated:NO completion:nil];
// [self presentViewController:cameraView animated:NO completion:nil]; //this will cause view is not in the window hierarchy error
}
If you have AVPlayer object with played video you have to pause video first.
I had the same issue. The problem was, the performSegueWithIdentifier was triggered by a notification, as soon as I put the notification on the main thread the warning message was gone.
It's working fine try this.Link
UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:secondView animated:YES completion: nil];
In case it helps anyone, my issue was extremely silly. Totally my fault of course. A notification was triggering a method that was calling the modal. But I wasn't removing the notification correctly, so at some point, I would have more than one notification, so the modal would get called multiple times. Of course, after you call the modal once, the viewcontroller that calls it it's not longer in the view hierarchy, that's why we see this issue. My situation caused a bunch of other issue too, as you would expect.
So to summarize, whatever you're doing make sure the modal is not being called more than once.
I've ended up with such a code that finally works to me (Swift), considering you want to display some viewController from virtually anywhere. This code will obviously crash when there is no rootViewController available, that's the open ending. It also does not include usually required switch to UI thread using
dispatch_sync(dispatch_get_main_queue(), {
guard !NSBundle.mainBundle().bundlePath.hasSuffix(".appex") else {
return; // skip operation when embedded to App Extension
}
if let delegate = UIApplication.sharedApplication().delegate {
delegate.window!!.rootViewController?.presentViewController(viewController, animated: true, completion: { () -> Void in
// optional completion code
})
}
}
This kind of warning can mean that You're trying to present new View Controller through Navigation Controller while this Navigation Controller is currently presenting another View Controller. To fix it You have to dismiss currently presented View Controller at first and on completion present the new one.
Another cause of the warning can be trying to present View Controller on thread another than main.
I fixed it by moving the start() function inside the dismiss completion block:
self.tabBarController.dismiss(animated: false) {
self.start()
}
Start contains two calls to self.present() one for a UINavigationController and another one for a UIImagePickerController.
That fixed it for me.
I fixed this error with storing top most viewcontroller into constant which is found within while cycle over rootViewController:
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.present(controller, animated: false, completion: nil)
// topController should now be your topmost view controller
}
You can also get this warning when performing a segue from a view controller that is embedded in a container. The correct solution is to use segue from the parent of container, not from container's view controller.
Have to write below line.
self.searchController.definesPresentationContext = true
instead of
self.definesPresentationContext = true
in UIViewController
With Swift 3...
Another possible cause to this, which happened to me, was having a segue from a tableViewCell to another ViewController on the Storyboard. I also used override func prepare(for segue: UIStoryboardSegue, sender: Any?) {} when the cell was clicked.
I fixed this issue by making a segue from ViewController to ViewController.
I had this issue, and the root cause was subscribing to a button click handler (TouchUpInside) multiple times.
It was subscribing in ViewWillAppear, which was being called multiple times since we had added navigation to go to another controller, and then unwind back to it.
It happened to me that the segue in the storyboard was some kind of broken. Deleting the segue (and creating the exact same segue again) solved the issue.
With your main window, there will likely always be times with transitions that are incompatible with presenting an alert. In order to allow presenting alerts at any time in your application lifecycle, you should have a separate window to do the job.
/// independant window for alerts
#interface AlertWindow: UIWindow
+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message;
#end
#implementation AlertWindow
+ (AlertWindow *)sharedInstance
{
static AlertWindow *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[AlertWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
});
return sharedInstance;
}
+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message
{
// Using a separate window to solve "Warning: Attempt to present <UIAlertController> on <UIViewController> whose view is not in the window hierarchy!"
UIWindow *shared = AlertWindow.sharedInstance;
shared.userInteractionEnabled = YES;
UIViewController *root = shared.rootViewController;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
alert.modalInPopover = true;
[alert addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
shared.userInteractionEnabled = NO;
[root dismissViewControllerAnimated:YES completion:nil];
}]];
[root presentViewController:alert animated:YES completion:nil];
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
self.userInteractionEnabled = NO;
self.windowLevel = CGFLOAT_MAX;
self.backgroundColor = UIColor.clearColor;
self.hidden = NO;
self.rootViewController = UIViewController.new;
[NSNotificationCenter.defaultCenter addObserver:self
selector:#selector(bringWindowToTop:)
name:UIWindowDidBecomeVisibleNotification
object:nil];
return self;
}
/// Bring AlertWindow to top when another window is being shown.
- (void)bringWindowToTop:(NSNotification *)notification {
if (![notification.object isKindOfClass:[AlertWindow class]]) {
self.hidden = YES;
self.hidden = NO;
}
}
#end
Basic usage that, by design, will always succeed:
[AlertWindow presentAlertWithTitle:#"My title" message:#"My message"];
Sadly, the accepted solution did not work for my case. I was trying to navigate to a new View Controller right after unwind from another View Controller.
I found a solution by using a flag to indicate which unwind segue was called.
#IBAction func unwindFromAuthenticationWithSegue(segue: UIStoryboardSegue) {
self.shouldSegueToMainTabBar = true
}
#IBAction func unwindFromForgetPasswordWithSegue(segue: UIStoryboardSegue) {
self.shouldSegueToLogin = true
}
Then present the wanted VC with present(_ viewControllerToPresent: UIViewController)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if self.shouldSegueToMainTabBar {
let mainTabBarController = storyboard.instantiateViewController(withIdentifier: "mainTabBarVC") as! MainTabBarController
self.present(mainTabBarController, animated: true)
self.shouldSegueToMainTabBar = false
}
if self.shouldSegueToLogin {
let loginController = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
self.present(loginController, animated: true)
self.shouldSegueToLogin = false
}
}
Basically, the above code will let me catch the unwind from login/SignUp VC and navigate to the dashboard, or catch the unwind action from forget password VC and navigate to the login page.
I found this bug arrived after updating Xcode, I believe to Swift 5. The problem was happening when I programatically launched a segue directly after unwinding a view controller.
The solution arrived while fixing a related bug, which is that the user was now able to unwind segues by swiping down the page. This broke the logic of my program.
It was fixed by changing the Presentation mode on all the view controllers from Automatic to Full Screen.
You can do it in the attributes panel in interface builder. Or see this answer for how to do it programatically.
Swift 5
I call present in viewDidLayoutSubviews as presenting in viewDidAppear causes a split second showing of the view controller before the modal is loaded which looks like an ugly glitch
make sure to check for the window existence and execute code just once
var alreadyPresentedVCOnDisplay = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// we call present in viewDidLayoutSubviews as
// presenting in viewDidAppear causes a split second showing
// of the view controller before the modal is loaded
guard let _ = view?.window else {
// window must be assigned
return
}
if !alreadyPresentedVCOnDisplay {
alreadyPresentedVCOnDisplay = true
present(...)
}
}

iOS How to confirm 'Delete' for UIPreviewActions on images from CollectionView

Similar to Apple's Photo App, I'm using peek and pop on a collectionView of images. When you peek on a cell, you are shown the image on a PhotoViewController that just shows that image in an imageView. Apple provides 4 different preview actions: Copy, Share, Favourite, Delete. If the user hits 'Delete', the preview actions are updated to: Delete and Cancel. The peek image still shows.
In my own code, I can setup the preview actions and delete the photo but am not able to confirm the deletion before it occurs. I also cannot figure out how to keep the peek view open (or reopen it) with the new preview actions of Delete or Cancel.
Question: How can I get the user to confirm the deletion before it happens?
I tried putting a UIAlertController inside the UIPreviewAction but get a warning: "Attempt to present on whose view is not in the window hierarchy!" I tried a few alternatives such as using keyWindow to present the alertController but couldn't get that to work quickly. I assuming I can set another notification to the collection view so that when the peek closes with a 'Delete', an alert controller appears asking the user to confirm the deletion but by that stage the image no longer appears.
Any suggestions? Here are the alternatives I've used so far.
UIPreviewAction *delete = [UIPreviewAction actionWithTitle:#"Delete" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController)
{
//create alertController
...
// 1. present alertController:
[self presentViewController:alertController animated:YES completion:nil];
// 2. Alternatively tried to present alertController using keyWindow
UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:alertController animated:YES completion: nil];
//3. notify the presenting view controller that the user has deleted the image and set up an alert to occur after the peek view closes.
[[NSNotificationCenter defaultCenter] postNotificationName:#"deleteAlert" object:nil];
}
I know it is an old questions, but the solution is quite easy, so posting it here!
Swift 3
let closeTabAction = UIPreviewAction(title: "Close Tab", style: .destructive, handler: { (action, viewController) in
//this is confirmation action
})
let closeTabCancelAction = UIPreviewAction(title: "Cancel", style: .default, handler: { (action, viewController) in
//cancel action, don't need anything here
})
//UIPreviewActionGroup, this is the main action
let closeTabGroup = UIPreviewActionGroup(title: "Close Tab", style: .destructive, actions: [closeTabAction, closeTabCancelAction])
return [closeTabGroup]
Basically, you just need to use the UIPreviewActionGroup to create submenus. Apple Docs

Warning :-Presenting view controllers on detached view controllers is discouraged

In my app, I am using a navigation controller. Later on in some view I am using presentViewController for showing a zoomed image.
Also I am not using a Storyboard or nib.
I am getting this error in iOS 7 only. It works fine in iOS 6 and earlier:
Presenting view controllers on detached view controllers is
discouraged
To avoid getting the warning in a push navigation, you can directly use :
[self.view.window.rootViewController presentViewController:viewController animated:YES completion:nil];
And then in your modal view controller, when everything is finished, you can just call :
[self dismissViewControllerAnimated:YES completion:nil];
Wait for viewDidAppear():
This error can also arise if you are trying to present view controller before view actually did appear, for example presenting view in viewWillAppear() or earlier.
Try to present another view after viewDidAppear() or inside of it.
The reason of this warning is i was presenting a view controller over a small view that is not full size view. Given below is the image of my project. where on click on four option above. User navigate to different childviewcontroller's view.(it works like tabViewcontroller). But the childviewcontroller contains view of small size. So if we present a view from childviewcontroller it gives this warning.
And to avoid this, you can present a view on childviewcontroller's parent
[self.parentViewController presentViewController:viewController animated:YES completion:nil];
In my case, I've a sampleViewController's view added as a subview, then tries to present a popover from the view of sampleViewController (here self instead a UIViewController instance):
[self.view addSubview:sampleViewController.view];
The right way should be below:
// make sure the vc has been added as a child view controller as well
[self addChildViewController:sampleViewController];
[self.view addSubview:sampleViewController.view];
[sampleViewController didMoveToParentViewController:self];
B.t.w., this also works for the case that present a popover form a tableview cell, you just need to make sure the tableview controller has been added as child view controller as well.
Swift 3
For anyone stumbling on this, here is the swift answer.
self.parent?.present(viewController, animated: true, completion: nil)
I think that the problem is that you do not have a proper view controller hierarchy. Set the rootviewcontroller of the app and then show new views by pushing or presenting new view controllers on them. Let each view controller manage their views. Only container view controllers, like the tabbarviewcontroller, should ever add other view controllers views to their own views. Read the view controllers programming guide to learn more on how to use view controllers properly. https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/
I have almost the same problem. The reason was that I tried to present "some" controller on another and after animation was completed I was setting presented controller as root. After this operation all further controllers presenting bring me to the warning: "Presenting view controllers on detached view controllers is discouraged". And I solve this warning just settings "some" controller as root without any presentation at the begin.
Removed:
[[self rootController] presentViewController:controller animated:YES completion:^{
[self window].rootViewController = controller;
[[self window] makeKeyAndVisible];}];
Just make as root without any presentation:
[[self window] setRootViewController:controller];
One of the solution to this is if you have childviewcontroller So you simply presentviewcontroller on its parent by given
[self.parentViewController presentViewController:viewController animated:YES completion:nil];
And for dismiss use the same dismissview controller.
[self dismissViewControllerAnimated:YES completion:nil];
This is perfect solution works for me.
Use [self.navigationController presentViewController:xxx animated:YES completion:nil] in iOS 8.
In Swift 4.1 and Xcode 9.4.1
The solution is
DispatchQueue.main.async(execute: {
self.present(alert, animated: true)
})
If write like this i'm getting same error
let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: { action in
})
alert.addAction(defaultAction)
present(alert, animated: true, completion: nil)
I'm getting same error
Presenting view controllers on detached view controllers is discouraged <MyAppName.ViewController: 0x7fa95560Z070>.
Complete solution is
let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: { action in
})
alert.addAction(defaultAction)
//Made Changes here
DispatchQueue.main.async(execute: {
self.present(alert, animated: true)
})
Try this code
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:<your ViewController object>];
[self.view.window.rootViewController presentViewController:navigationController animated:YES completion:nil];
Try presenting on TabBarController if it is a TabBarController based app .
[self.tabBarController presentViewController:viewController animated:YES completion:nil];
Reason could be self is child of TabBarController and you are trying to present from a ChildViewController.
Yes, I also faced the same warning message while displaying an Alert controller which was in another view. Later on I avoided this by presenting the alert controller from the parent view controller as below:
[self.parentViewController presentViewController:alertController animated:YES completion:nil];
you need to add the view controller that will present the new controller as a child of the parent view controller.
Let's say you have yourMainViewController, then you add a new controller called controllerA, and then you want to present a new controller called controllerB from controllerA
you have to write something like this:
[self addChildViewController:controllerA]; //self is yourMainViewController
[self.view addsubView:controllerA.view];
and within controllerA you can present the new controller without warnings
[self presentViewController:controllerB animated:YES completion:nil]; //self is controllerA
I reached on this thread where I have a Custom Navigation Bar and I was calling an AlertViewController through it.
I had to add it as a child to my main view controller. Then I could call present it without any warning.
You should add your Zoomed Image View Controller as a child of the main ViewController.
(e.g)
[self addChildViewController:ZoomedImageViewController];
Then you'd be able to call your ZoomedImageViewController
[self presentViewController:ZoomedImageViewController];
Make sure you have a root view controller to start with. You can set it in didFinishLaunchingWithOptions.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window setRootViewController:viewController];
}
Lots of reasons for this warning. Mine is because I have a segue connected from a ViewController to another that will be presented modally. But, the ViewController I am presenting from is being dynamically generated by a PageViewController. Which is why it is detached in the Storyboard. My app isn't going to crash because of it; but I would like to silence the warning.
It depends if you want to show your alert or something similar in anywhere of kind UIViewController.
You can use this code example:
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Alert" message:#"Example" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:cancelAction];
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:alert animated:true completion:nil];
Many answers are right.
Check your presentingViewController have parentViewController or not.
If no, add it to somewhere it should be added
else, check it's parentViewController has parentViewController recursively until every viewController has parent
This issue happened to me when my co-worker add a AViewController to BViewController. Somehow, he just add the AViewController's view to BViewController's view.
Fixed by add bViewController.addChild(aViewController)

Dismissing a Modal View Controller using the UI Alert View Button

I am new to Xcode somewhat. Right now I am creating a view controller (displaying modally) which displays a form for users to input information, and click "submit" to submit the information.
I created my IBAction, and implemented a UIAlerView that informs the user the information has been sent. I would like the "Ok" button within my alert view to take them back to the original view controller. I set my Alert View delegate and implemented the following method in my .m file:
- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
When I test it, nothing happens. Can anyone tell me what I am doing wrong.
You need to implement - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex delegate method ---
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//dissmiss here
//Pre iOS 6.0
[self dismissModalViewControllerAnimated:YES];
//From iOS 5.0
[self dismissViewControllerAnimated:YES completion:nil];
}
You can also check for the button which tapped,
if(buttonIndex != [alertView cancelButtonIndex]) {
//do something
}
You need to implement the below mentioned delegate method of UIAlertView:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
if ([buttonTitle isEqual:#"Ok"]) // Check for Ok button
{
[self dismissViewControllerAnimated:YES completion:nil];
}
}
SWIFT 5
I have been away form Xcode since swift 3 and needed to do this in my new project. The below code worked for me (it presents an alert and upon closing the alert, the user is taken back to the previous view controller):
func displayAlert() {
let alert = UIAlertController(title: "Your Title",
message: "Your Message",
preferredStyle: .alert)
let defaultButton = UIAlertAction(title: "OK",
style: .default) {(_) in
// your defaultButton action goes here
self.dismiss(animated: true, completion: nil)
}
alert.addAction(defaultButton)
present(alert, animated: true) {
// completion goes here
}
}

Resources