How to dismiss pushed ViewController after logout? - ios

I spent so many hours with this issue, so I can't find any solution.
I open SettingsViewController from my TopBar on tap in avatar with pushViewController method (here is problem) and from menu (not modal).
I want to dismiss this pushed ViewController when I tap on logout button.
Below is a function that I use in some VC's and works very well.
func goToSettingsView() {
let vc = SettingsViewController(nibName: "SettingsViewController", bundle: nil)
vc.modalPresentationStyle = .fullScreen
self.navigationController!.pushViewController(vc, animated: true)
for constraint in vc.view.constraints {
if constraint.identifier == "alignTopHeader" {
constraint.constant = 0
}
}
}
When I clicked logout button isn't working and when I login from (LoginViewController) this SettingsViewController is still showing, but I would go to Main Screen without any modals.
I did some ideas but not good working yet.
First idea was:
Below is my logout IBaction in my SettingsViewController:
- (void)logout {
[self dismissViewControllerAnimated:YES completion:nil];
[[UserManager shared] logoutUserWithSuccessBlock:^{
[self presentLoginViewController];
}];
}
LoginViewController is dismiss, but self is targeted for SettingsViewController?
Second idea:
I added a function declared in AppDelegate "backToRoot" in LoginViewController and call from viewWillDisappear.
[appDelegate backToRoot];
function in AppDelegate.m file
-(void)backToRoot {
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
PresentationsPickerViewController *mainvc = [[PresentationsPickerViewController alloc] init];
[self setCenterPanelWithViewController:mainvc];
}
But still not working with modal, it's work fine when SettingsViewController isn't modal.
Do you have any ideas how to hide/dismiss pushed SettingsViewController in logout action?

I want to dismiss this pushed ViewController when I tap on logout button.
Now you don't dismiss a pushed UIViewController. Push is a navigation style associated with a UINavigationController. When you push a UIViewController you need to pop it.
When we talk about dismiss, we usually mean closing a UIViewController that is presented modally. This method means that a new UIViewController is added on top of your previously shown UIViewController or any class inheriting from it. This does not get added onto the navigation stack. You can only dismiss a UIViewController if it was presented.
Now your first code block shows you pushing the SettingsViewController and your first solution tries to dismiss it. This will not work. You need to pop it from the UINavigationController to close it.
Next,
LoginViewController is dismiss, but self is targeted for SettingsViewController?
The method [self dismiss]... will close the screen that is the latest presented screen on top. If you present LoginViewController from settings, then LoginViewController screen gets dismissed.
Also,
But still not working with modal, it's work fine when SettingsViewController isn't modal. Do you have any ideas how to hide/dismiss pushed SettingsViewController in logout action?
If your SettingsViewController is presented then you need to dismiss it and if it is pushed, you need to pop it.
If there are situations when both the actions can occur, then on your close button action, you can check how the screen was displayed. Use this link to figure out the checks for that.
If all you want to do is go to your LoginViewController on logging out, you can just change the root window of your application.
let window = (UIApplication.shared.delegate as? AppDelegate)?.window
window?.rootViewController = viewController //this will be your loginViewController
window?.makeKeyAndVisible()
For iOS 13:
UIApplication.shared.windows.first?.rootViewController = LoginViewController
UIApplication.shared.windows.first?.makeKeyAndVisible()

It's really simple. If you want to dismiss a pushed UIViewController in a navigational-stack just pop it out, like this:
func logout() {
navigationController?.popViewController(animated: true)
}

You can simple dismiss all view controllers above the root view controller.
func logout() {
self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)
}
Hope it will help. Thanks.

Related

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(...)
}
}

Not able to navigate to another UIViewController programmatically iOS

I am trying to navigate to "Home" view controller and for this I have written the following code in the ContainerViewController. But once the code executes, the application hangs and it show 100% CPU usage. Please help.
- (IBAction) home:(UIButton *)sender
{
HomeViewController *homeViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"HomeViewController"];
[self.navigationController pushViewController:homeViewController animated:YES];
//[self presentViewController:homeViewController animated:YES completion:nil];
}
I have a question for you
1-If You want to push SecondViewController on to FirstViewController then your code is good enough
2-If you have a containerview in firstViewController and you want to add SecondViewcontroller's view to firstViewController
then use this code
UIViewController*vc1 = [[test1 alloc]initWithNibName:#"SecondViewController" bundle:nil];
//add to the container vc which is self
[self addChildViewController:vc1];
//the entry view (will be removed from it superview later by the api)
[self.view addSubview:vc1.view];
I think you want an unwind segue here. In your first view controller add :
- (IBAction)unwindToFirstViewController:(UIStoryboardSegue*)sender
{
}
You then need to hook up each of your view controllers home button to the green Exit button at the bottom of the view controller, choosing the unwindToMainMenu option. This will then take you back to the first view controller when pressed.
Have you tried popping the current view?
navigationController?.popViewControllerAnimated(true)
or just popping to root?
navigationController?.popToRootViewControllerAnimated(true)
or setting a new stack?
navigationController?.setViewControllers(homeViewController, animated: true)
The code is in Swift but it would work the same in ObjectiveC

how to pop view from stack in tab bar controller in iOS

I have a tabbar controller. In one of the viewcontroller children I do a check and determine if I will show a login view with this code:
if(loggedIn){
}else{
SignupViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SignupView"];
svc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:svc animated:YES];
}
From here the user goes through several subsequent views in wizard format. Like from SignupViewController1->SignupViewController2->SignupViewController3->etc. As #Rocky pointed out, you cannot pop SignupViewController1 off the stack while you are in SignupViewController2 or 3 or any subsequent viewController.
I know the iOS docs state the following:
If YES, the bottom bar remains hidden until the view controller is
popped from the stack.
My question is how do I get acccess to the original SignupViewController to pop it off the stack to see my tabbar again once I have moved to subsequent views in its navigation controller?
(The next answer is for SWIFT, but, you can traduce it to Objective-C)
I do not know, but maybe you can try that:
#IBAction func returnToPreviusScene(sender : AnyObject) {
let navController:UINavigationController = self.navigationController!
navController.popViewControllerAnimated(true)
navController.pushViewController(FatherView.singleton, animated: false)
}
You need to know:
singleton is a static variable from the VIEW FatherView

Login View Controller over modal View Controller

The following scenario:
My iPad app has a SplitViewController as it's main VC. After starting the app (new or from background) I have a fullscreen login view that (obviously) disappears after entering the correct password.
The problem:
After login, I want to present the exact same screen that was there BEFORE moving to background. This works fine UNLESS there is a modal view on top of the split view (like settings etc).
What I tried:
In AppDelegate I store my self.window.rootViewController, make the login vc as my root vc and after login I set my stored root VC as actual root VC. But then the (modal) settings view is not visible and can not be opened again (Warning: Attempt to present VC on SplitVC which is already presenting VC). In fact, no other modal view can ever be opened (unless app is properly closed).
Second try: Instead of setting the login VC as root VC I presented it as a fullscreen modal view on top of my split view. This yielded the same error message as the first try but a different result. After entering background mode the login VC won't be presented at all (since there already was a modal view).
This is b'coz you R trying to present the VC while it is actually loaded as RootViewController .
Try using this :
UISplitViewController :
Once loaded the Root and as well as MasterViewControlller , You will make the UIViewControllers as SubViews for Your
RootViewController.. From the UIViewController , If you want to revert
back to the RootViewController , Try this :
[self.navigationController popToRootViewControllerAnimated:NO];
I just thought your problem is similar.
Hope it helps.
What I did was the following:
Create a property that can store my modally presented VCs (they are all embedded in a UINavigationController)
#property (nonatomic) UINavigationController *navController;
When creating the login vc I store my modal vc (which may be nil which is fine), dismiss it and present the login vc
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
LoginViewController *loginViewController = [storyboard instantiateViewControllerWithIdentifier:#"LoginView"];
self.navController = (UINavigationController *)self.window.rootViewController.presentedViewController;
if (self.navController) {
[self.navController dismissViewControllerAnimated:NO completion:nil];
}
[self.window.rootViewController presentViewController:loginViewController animated:NO completion:nil];
And when the login is successfull I dismiss the login vc and restore the modal vc (if available)
if (self.navController) {
[self.window.rootViewController presentViewController:self.navController animated:NO completion:nil];
}
Can you try this way.
Root VC is main screen not login page.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
Inside the above code, present your login page to your root VC.
- (void)applicationDidBecomeActive:(UIApplication *)application{
//Use below method or similar method to remove any presented VC on Root VC
if ([((UINavigationController *)self.window.rootViewController).visibleViewController isKindOfClass:[RLSplashViewController class]]) {
NSLog(#"AppDelegate dismiss splash page");
[self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
}
//And present your login VC
}
Inside the above code, First and very import, remove any VC that presented on the root VC. And then present login page to your root VC.
I am currently using this way to present splash page(from background or new), in your case, it is login page. Hopes this is helpful for your case : )

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