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

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)

Related

Can't dismiss alerts programmatically in Mac Catalyst?

I'm showing an alert like this:
self.connectingAlert = [UIAlertController
alertControllerWithTitle:#""
message:NSLocalizedString(#"CONNECTING", nil)
preferredStyle:UIAlertControllerStyleAlert];
[self.connectingAlert addAction:[UIAlertAction
actionWithTitle:NSLocalizedString(#"BUTTON_CANCEL", nil)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
[self cancelRequest];
}]];
[self presentViewController:self.connectingAlert animated:TRUE completion:nil];
Then later I want to dismiss the alert programmatically like this:
[self dismissViewControllerAnimated:FALSE completion:nil];
The dismiss code works fine in iOS, but does nothing in Mac Catalyst. This might have something to do with the fact that alerts are presented as a part of the app window, sort of outside of the app, and the presentation style is ignored. But I would expect the dismiss method to still affect the Mac alerts.
I tried this to make sure everything is hooked up correctly:
UIViewController *test1 = self.connectingAlert.presentingViewController;
UIViewController *test2 = self.connectingAlert.presentingViewController.presentedViewController;
test1 returns the navigation controller that the view controller is part of, which seems odd, but it does the same thing on iOS. test2 returns my alert. Just to make sure, I tried this code, but it doesn't work either:
[self.connectingAlert.presentingViewController dismissViewControllerAnimated:FALSE completion:nil];
Does anyone have experience with this? I don't see anything about it in the documentation.
It turns out that while you're supposed to send the dismiss message to the presenting (parent) view controller...
[self dismissViewControllerAnimated:FALSE completion:nil];
...I have to send the dismiss message to the alert instead, and then it dismisses:
[self.connectingAlert dismissViewControllerAnimated:FALSE completion:nil];
The documentation for dismissViewControllerAnimated says:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, UIKit asks the presenting view controller to handle
the dismissal.
I think what this means is that Mac Catalyst is doing something funny with the connection between the presenting and the presented view controller. If I check self.presentedViewController, that gives me the UIAlertController. But if I call self dismiss dismissViewControllerAnimated..., nothing happens, as if there is no presented view controller. But if I call self.connectingAlert dismissViewControllerAnimated..., the dismiss method somehow finds its way to the real presenting view controller. I'll report this as a bug to Apple.
Meanwhile, I'm happy to have a workaround.

viewWillAppear of parent triggered when second presented viewcontroller dismissed

I have 3 viewcontrollers
let's say AVC, BVC and CVC
First I present BVC from AVC and then present CVC from BVC
So the hierarchy would be AVC->BVC->CVC.
When I dismiss CVC from BVC, viewWillAppear of AVC is triggered, how can I prevent viewWillAppear of AVC getting called?
Thanks
UPDATE
First present (in AVC)
BTViewController *BTVC = [self.storyboard instantiateViewControllerWithIdentifier:#"BTVCIns"];
__weak BTViewController *weakBTVC = BTVC;
BTVC.dismissAction = ^{[weakBuyTicketVC dismissViewControllerAnimated:YES completion:nil];};
[BTVC setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self presentViewController:BTVC animated:YES completion:nil];
Second present (in BVC)
PMViewController *PMVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PMVCIns"];
__block PMViewController *blockPMVC = PMVC;
blockPMVC.dismissAction = ^{[blockPMVC dismissViewControllerAnimated:YES completion:nil];};
blockPMVC.delegate = self;
[self presentViewController:PMVC animated:YES completion:nil];
From the View Controller Programming Guide for iOS
When presenting a view controller using the UIModalPresentationFullScreen style, UIKit normally removes the views of the underlying view controller after the transition animations finish. You can prevent the removal of those views by specifying the UIModalPresentationOverCurrentContext style instead. You might use that style when the presented view controller has transparent areas that let underlying content show through.
Since you are using UIModalPresentationOverCurrentContext, iOS considers that BVC may have transparent areas which expose AVC, hence when BVC appears it is possible that AVC is also going to appear, so viewWillAppear is called.

Dismiss a UIViewController without dismissing an alert on top of it

I have a UIViewController that presents a UIAlertController. I would like the UIAlertController to dismiss itself while still allowing the UIAlertController to stay on top.
Here's the code to dismiss:
[self dismissViewControllerAnimated:YES completion:nil];
I'll omit the code for UIAlertController, but I'm using this code to present:
[self presentViewController:alert animated:YES completion:nil];
I'm looking for a solution that can be contained within my single UIViewController subclass.
I suppose your scenario is like this:
A_ViewController pushed B_ViewController, B_ViewController presented alertController at some circumstances. Now you're at B_ViewController and want to dismiss itself without the alert disappear,right?
If I got this right,I don't think this gonna work. Because alert depends on B_ViewController, if B is gone, so did alert.
Here is my thought:
A_ViewController pushed B_ViewController, now we're at B, at some circumstances there should be an alert being presented,instead of letting B present the alert, we could let A present the alert(We could use delegate method to tell A to present an alert). And now, B and alert are independent,B gone,alert stays.
I hope this would help.

Dismissing previously presented modal view controller before currently visible one

I have 2 UIViewController's presented with [self presentViewController:viewController animated:YES completion:nil];, I want to dismiss the first one of them, without animation (It's not visible to the user anyway) and when the second one (currently visible) will be dismissed, the user will see the parent view controller who present them both.
- Parent
- First -> Dismiss first without animation
- Second -> Dismiss second with animation
How can I do that?
With your current view controller hierarchy if first view controller will be dismissed it will dismiss second view controller automatically. If you don't want that behaviour than make parent present second view controller. You can do that from first view controller by using [self.presentingViewController presentViewController:secondViewController animated:YES completion:nil]
Why do you want to do this?
You should do it like this for cleaner view hierarchy and better user experience:
Present first view controller :
[self presentViewController:viewController1 animated:YES completion:nil];
Dismiss first & present second view controller :
__weak MyViewController *aBlockSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[aBlockSelf presentViewController:viewController2 animated:YES completion:nil];
}];

Can a presented view controller also be a presenting view controller?

On top of an existing view I want to:
a) display a screen to the user
b) then send an SMS
c) display another screen to the user.
For a) I am doing this:
[[UIApplication sharedApplication].delegate.window.rootViewController presentViewController: firstController animated: NO completion:nil];
and for b) I am doing the same thing, except this is presenting a different vc of course, a MFMessageComposeViewController.
However in order for b) to appear I first have to dismiss the first view controller using:
[[UIApplication sharedApplication].delegate.window.rootViewController dismissViewControllerAnimated:NO completion: nil];
That works so far, I can see the first view appear then see the SMS compose view appear.
When the SMS is sent I am doing this to dismiss the SMS compose view
[[UIApplication sharedApplication].delegate.window.rootViewController dismissViewControllerAnimated:NO completion: nil];
But then nothing happens when I try to present another screen to the user using presentViewController. I can't see any reason why this should be, is there something I'm not aware of?
Actually the screen before the SMS view and after it are the same except they have different text, so the easiest sequence of steps would be:
a) present the view controller with text "abc"
b) present the SMS controller
c) when the SMS is sent dismiss the SMS controller
d) update the text in the first view controller using an IBOutlet
e) dismiss the first view controller.
However as mentioned earlier on, if I don't dismiss the first view controller the SMS controller will not appear. So my main question is how can I present the SMS controller on top of the first view controller?
You can either present one after the other closes:
UIViewController *rvc = [UIApplication sharedApplication].delegate.window.rootViewController;
[rvc dismissViewControllerAnimated:NO completion:^{
[rvc presentViewController: secondController animated: NO completion:nil];
}];
Or present another on top:
UIViewController *rvc = [UIApplication sharedApplication].delegate.window.rootViewController;
UIViewController *pvc = rvc.presentedViewController; // you may need to loop through presentedViewControllers if you have more than one
[pvc presentViewController: secondController animated: NO completion:nil];
I just tried it on iOS15. Yes a presented VC can present another VC.
So suppose you have:
VC1 --> present--> VC2
you can easily call present(VC3(), animated: true, completion: nil) on VC2 and things would work fine. You can happily end up with:
VC1 --> present--> VC2 --present--> VC3
FWIW when you dismiss VC3, it will only go back to VC2. It won't go back to VC1.
iOS does not allow you to open two modal views at the same time. The modal view is designed to be the topmost view.
In my case, I have access directly to the presented view controller so in that case:
self.present(viewControllerToPresent, animated: true) {
//It's presented.
}
extension UIViewController{
/// most top presented view controller
var presentedTop:UIViewController {
var ctrl:UIViewController = self;
while let presented = ctrl.presentedViewController {
ctrl = presented;
}
return ctrl;
}
}
// call somewhere someCtrl.presentedTop.present(vc, animated:true)

Resources