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(...)
}
}
Related
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.
So I need some help with dismissing a page sheet modal screen in iOS 13. I have looked at several posts and none helped.
iOS 13 Modals - Calling swipe dismissal programmatically
Looking at the 2nd answer I tried that but I kept getting that self was implicit and needed to be explicit. Tried researching that but didn't find to much.
All I want to do is have a Done button in the upper right of nav bar. When that button is pushed I need for it to connect to the database and save the data if it is valid (already implemented). Then it needs to dismiss the modal screen and refresh the table so that it gets the newest changes.
I have read up on delegates but didn't help much. I read a bit up on UIAdaptivePresentationControllerDelegate and I understand the basics behind it and being able to pull to close the modal. But not sure how to duplicate that in the button. I did remember reading to set the presentation delegate. Or something to that effect.
let navigationController = segue.destination as! UINavigationController
let editSensorVC = navigationController.topViewController as! EditSensorViewController
navigationController.presentationController?.delegate = editSensorVC
It was saying this was how I set the Delegate, but not sure where to go from here.
If the originating VC is of type PresentingVC, and the modal of type PresentedVC, I'd use the below approach. Given the segue statement above I assume you're using storyboards, but if not replace the prepare(for segue:) with injecting the delegate value when you instantiate yourPresentedVC
For starters, set your PresentedVC up to hold a delegate by defining the delegate protocol and providing a delegate property.
protocol PresentedVCDelegate {
func presentedVCDidUpdateDatabase()
}
class PresentedVC {
var delegate: PresentedVCDelegate?
#IBAction buttontapped(_ sender: Any) {
//existing code to validate and save data to databse
delegate?. presentedVCDidUpdateDatabase()
dismiss(animated: true)
}
}
Update the PresentingVC so that it injects itself as the delegate when instantiating its child VC:
class PresentingVC {
//all the current code
// and amend preapre(for:) something like
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller.
if let presented = segue.destination as? PresentedVC {
presented.delegate = self
//anything else you do already
}
}
}
The extend it to support the protocol method
extension PresentingVC: PresentedVCDelegate {
func presentedVCDidUpdateDatabase() {
tableView.reloadData()
//any other work necessary after PresentedVC exits
}
}
Note: written from memory and not compiled, so may contain minor typos, but hopefully it's enough detail to get the concept across?
here, this is how you do it objective-c in the presenting viewcontroller that really isn't presenting anything but it's navigationcontroller is. this is the easy mode.
UIViewController *pvc = [UIViewController new];
WLGNavigationController *nav = [[WLGNavigationController alloc] initWithRootViewController:pvc];
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(backerPressed)];
[[pvc navigationItem] setLeftBarButtonItem:backBarButtonItem];
[[self navigationController] presentViewController:nav animated:true completion:nil];
with backer pressed being this:
- (void)backerPressed {
[[self navigationController] dismissViewControllerAnimated:true completion:nil];
}
for the hard hard modes:
completion handler:
add a completion handler to the VC you're presenting, then in the VC you presented, you add the navigation button then add an action that when pressed, you run that completion handler after the promise is executed as finished. then in the presenting view controller, you set up the completion handling code to then dismiss the presented navigation/view controller combo.
Delegation:
write a delegate in the VC you're presenting, when the promise returns, execute the delegate methods, "responds to selector" blah blah, like you'd normally do with delegation. in the presenting VC you then make it the delegate of the VC you're presenting, implement the method that when called calls for the navigation controller to dismiss it's presented navigation/viewcontroller package.
I have a small iPhone app, which uses a navigation controller to display 3 views (here fullscreen):
First it displays a list of social networks (Facebook, Google+, etc.):
Then it displays an OAuth dialog asking for credentials:
And (after that, in same UIWebView) for permissions:
Finally it displays the last view controller with user details (in the real app this will be the menu, where the multiplayer game can be started):
This all works well, but I have a problem, when the user wants to go back and select another social network:
The user touches the back button and instead of being displayed the first view, is displayed the second one, asking for OAuth credentials/permissions again.
What can I do here? Xcode 5.0.2 shows a very limited choice for segues - push, modal (which I can't use, because it obscures navigation bar needed for my game) and custom.
I am an iOS programming newbie, but earlier I have developed an Adobe AIR mobile app and there it was possible to 1) replace view instead of pushing and 2) remove an unneeded view from navigation stack.
How to do the same in a native app please?
To expand on the various segues above, this is my solution. It has the following advantages:
Can work anywhere in the view stack, not just the top view (not sure if this is realistically ever needed or even technically possible to trigger, but hey it's in there).
It doesn't cause a pop OR transition to the previous view controller before displaying the replacement, it just displays the new controller with a natural transition, with the back navigation being to the same back navigation of the source controller.
Segue Code:
- (void)perform {
// Grab Variables for readability
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
// Get a changeable copy of the stack
NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
// Replace the source controller with the destination controller, wherever the source may be
[controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];
// Assign the updated stack with animation
[navigationController setViewControllers:controllerStack animated:YES];
}
You could use a custom segue: to do it you need to create a class subclassing UIStoryboardSegue (example MyCustomSegue), and then you can override the "perform" with something like this
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
// Pop to root view controller (not animated) before pushing
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:destinationController animated:YES];
}
At this point go to Interface Builder, select "custom" segue, and put the name of your class (example MyCustomSegue)
In swift
override func perform() {
if let navigationController = self.source.navigationController {
navigationController.setViewControllers([self.destination], animated: true)
}
}
The custom segue didn't work for me, as I had a Splash view controller and I wanted to replace it. Since there was just one view controller in the list, the popToRootViewController still left the Splash on the stack. I used the following code to replace the single controller
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
[navigationController setViewControllers:#[destinationController] animated:YES];
}
and now in Swift 4:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
source.navigationController?.setViewControllers([destination], animated: true)
}
}
and now in Swift 2.0
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
sourceViewController.navigationController?.setViewControllers([destinationViewController], animated: true)
}
}
For this problem, I think the answer is just simple as
Get the array of view controllers from NavigationController
Removing the last ViewController (current view controller)
Insert a new one at last
Then set the array of ViewControllers back to the navigationController
as bellow:
if let navController = self.navigationController {
let newVC = DestinationViewController(nibName: "DestinationViewController", bundle: nil)
var stack = navController.viewControllers
stack.remove(at: stack.count - 1) // remove current VC
stack.insert(newVC, at: stack.count) // add the new one
navController.setViewControllers(stack, animated: true) // boom!
}
works perfectly with Swift 3.
Hope it helps for some new guys.
Cheers.
the swift 2 version of ima747 answer:
override func perform() {
let navigationController: UINavigationController = sourceViewController.navigationController!;
var controllerStack = navigationController.viewControllers;
let index = controllerStack.indexOf(sourceViewController);
controllerStack[index!] = destinationViewController
navigationController.setViewControllers(controllerStack, animated: true);
}
As he mentioned it has the following advantages:
Can work anywhere in the view stack, not just the top view (not sure if this is realistically ever needed or even technically possible to trigger, but hey it's in there).
It doesn't cause a pop OR transition to the previous view controller before displaying the replacement, it just displays the new controller with a natural transition, with the back navigation being to the same back navigation of the source controller.
Using unwind segue would be the most appropriate solution to this problem. I agree with Lauro.
Here is a brief explanation to setup an unwind segue from detailsViewController[or viewController3] to myAuthViewController[or viewController1]
This is essentially how you would go about performing an unwind segue through the code.
Implement an IBAction method in the viewController you want to unwind to(in this case viewController1). The method name can be anything so long that it takes one argument of the type UIStoryboardSegue.
#IBAction func unwindToMyAuth(segue: UIStoryboardSegue) {
println("segue with ID: %#", segue.Identifier)
}
Link this method in the viewController(3) you want to unwind from. To link, right click(double finger tap) on the exit icon at the top of the viewController, at this point 'unwindToMyAuth' method will show in the pop up box. Control click from this method to the first icon, the viewController icon(also present at the top of the viewController, in the same row as the exit icon). Select the 'manual' option that pops up.
In the Document outline, for the same view(viewController3), select the unwind segue you just created. Go to the attributed inspector and assign a unique identifier for this unwind segue. We now have a generic unwind segue ready to be used.
Now, the unwind segue can be performed just like any other segue from the code.
performSegueWithIdentifier("unwind.to.myauth", sender: nil)
This approach, will take you from viewController3 to viewController1 without the need to remove viewController2 from the navigation hierarchy.
Unlike other segues, unwind segues do not instantiate a view controller, they only go to an existing view controller in the navigation hierarchy.
As mentioned in previous answers to pop not animated and then to push animated won't look very good because user will see the actual process.
I recommend you first push animated and then remove the previous vc. Like so:
extension UINavigationController {
func replaceCurrentViewController(with viewController: UIViewController, animated: Bool) {
pushViewController(viewController, animated: animated)
let indexToRemove = viewControllers.count - 2
if indexToRemove >= 0 {
viewControllers.remove(at: indexToRemove)
}
}
}
Here a quick Swift 4/5 solution by creating a custom seque that replaces the (top stack) viewcontroller with the new one (without animation) :
class SegueNavigationReplaceTop: UIStoryboardSegue {
override func perform () {
guard let navigationController = source.navigationController else { return }
navigationController.popViewController(animated: false)
navigationController.pushViewController(destination, animated: false)
}
}
Use below code last view controller
You can use other button or put it your own instead of cancel button i have used
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(dismiss:)];
self.navigationItemSetting.leftBarButtonItem = cancelButton;
}
- (IBAction)dismissSettings:(id)sender
{
// your logout code for social media selected
[self.navigationController popToRootViewControllerAnimated:YES];
}
What you should really do is modally present a UINavigationController containing the social network UIViewControllers overtop of your Menu UIViewController (which can be embedded in a UINavigationController if you want). Then, once a user has authenticated, you dismiss the social network UINavigationController, showing your Menu UIViewController again.
In swift3 create one segue
-add identifier
-add and set in segue(storyboard) custom storyboard class from cocoatouch file
-In custom class override perform()
override func perform() {
let sourceViewController = self.source
let destinationController = self.destination
let navigationController = sourceViewController.navigationController
// Pop to root view controller (not animated) before pushing
if self.identifier == "your identifier"{
navigationController?.popViewController(animated: false)
navigationController?.pushViewController(destinationController, animated: true)
}
}
-You also have to override one method in your source viewcontroller
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return false
}
Well, what you can also do is to use the unwind view controller stuff.
Actually I think that this is exactly what you need.
Check this entry: What are Unwind segues for and how do you use them?
This worked for me in Swift 3:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
if let navVC = source.navigationController {
navVC.pushViewController(destination, animated: true)
} else {
super.perform()
}
}
}
How about this :) I now it's old question, but this will work as a charm:
UIViewController *destinationController = [[UIViewController alloc] init];
UINavigationController *newNavigation = [[UINavigationController alloc] init];
[newNavigation setViewControllers:#[destinationController]];
[[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation;
I have a PIN view controller which needs to be presented every time the - (void)applicationWillEnterForeground:(UIApplication *)application fires. I have multiple view controllers and when the app enters background the user could be on any of them.
The problem is I don't know how to present the PIN view controller over any view controller that is currently active. Here's how my implementation looks:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
ResourceSingleton *resource = [ResourceSingleton sharedSingleton];
if ([resource checkIfPINIsEnabled])
{
PinViewController *pinView = [[PinViewController alloc] initWithMode:kPINViewControllerModeEnter];
pinView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.window.rootViewController presentViewController:pinView animated:YES completion:NULL];
}
}
But the PIN view appears only if I'm at the first view controller (the root one). How to pop it up on any view controller?
I have seen Using applicationwillenterforeground for a passcode screen but there has to be a better way or am I wrong? This will be for iOS 7 so if only 7 has such a functionality its ok but I am pretty sure it can be done on 6 as well.
If your root view controller is a NavigationController, then pushing or presenting should work in most cases. You already have all the code in place, just create a navigation controller. The only case this would not work is if there is a modal view controller already presented. In that case that needs to be dismissed first.
Here is a little messy implementation that takes care of this case too.
AKPresentedViewController *pres = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"pres"];
UINavigationController *navi = ((UINavigationController*)self.window.rootViewController);
if (navi.presentedViewController) {
[navi.presentedViewController dismissViewControllerAnimated:YES completion:^{
[navi presentViewController:pres animated:NO completion:nil];
}];
} else {
[navi presentViewController:pres animated:NO completion:nil];
}
You could have the app delegate handle the logic for the PIN view, and have that be a view, rather than a view controller. Just add the view as a subview of the window, and it will be shown over anything else.
- (void)applicationWillEnterForeground:(UIApplication *)application {
UINib *pinNib = [UINib nibWithNibName:#"PINView" bundle:nil];
UIView *pinView = [pinNib instantiateWithOwner:self options:nil][0];
[self.window addSubview:pinView];
}
If you make the app delegate the File's Owner of the xib, then you can hook up any outlets you need in the view to the app delegate.
You could present your PIN view controller the way they mention here.
And for popping the PIN view controller I'm guessing that the user has to enter the correct PIN so the PIN view controller goes away. In that case it can pop itself:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
Hope this helps!
Convert to the objective-C codes to Swift 4, put the codes into func applicationWillEnterForeground
let pres = self.window?.rootViewController?.storyboard?.instantiateViewController(withIdentifier: "StartVC")
let navi: UINavigationController = self.window?.rootViewController as! UINavigationController
if ((navi.presentedViewController) != nil) {
navi.dismiss(animated: true) {
navi.present(pres!, animated: false, completion: nil)
}
} else {
navi.present(pres!, animated: false, completion: nil)
}
I read SO about another user encountering similar error, but this error is in different case.
I received this message when I added a View Controller initially:
Unbalanced calls to begin/end appearance transitions for
<UITabBarController: 0x197870>
The structure of the app is as follow:
I got a 5-tab TabBarController linked to 5 View Controllers. In the initial showing tab, I call out a new View Controller to overlay as an introduction of the app.
I use this code to call the introduction view controller:
IntroVC *vc = [[IntroVC alloc] init];
[self presentModalViewController:vc animated:YES];
[vc release];
After this IntroVC view controller shows up, the above error shows.
p.s. I am using xCode 4.2 & iOS 5.0 SDK, developing iOS 4.3 app.
Without seeing more of the surrounding code I can't give a definite answer, but I have two theories.
You're not using UIViewController's designated initializer initWithNibName:bundle:. Try using it instead of just init.
Also, self may be one of the tab bar controller's view controllers. Always present view controllers from the topmost view controller, which means in this case ask the tab bar controller to present the overlay view controller on behalf of the view controller. You can still keep any callback delegates to the real view controller, but you must have the tab bar controller present and dismiss.
I fixed this error by changing animated from YES to NO.
From:
[tabBarController presentModalViewController:viewController animated:YES];
To:
[tabBarController presentModalViewController:viewController animated:NO];
As posted by danh
You can generate this warning by presenting the modal vc before the app is done initializing. i.e. Start a tabbed application template app and present a modal vc on top of self.tabBarController as the last line in application:didFinishLaunching. Warning appears. Solution: let the stack unwind first, present the modal vc in another method, invoked with a performSelector withDelay:0.0
Try to move the method into the viewWillAppear and guard it so it does get executed just once (would recommend setting up a property)
Another solution for many cases is to make sure that the transition between UIViewControllers happens after the not-suitable (like during initialization) procedure finishes, by doing:
__weak MyViewController *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf presentViewController:vc animated:YES];
});
This is general for also pushViewController:animated:, etc.
I had the same problem. I called a method inside viewDidLoad inside my first UIViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:#selector(loadingView)
withObject:nil afterDelay:0.5];
}
- (void)loadingView{
[self performSegueWithIdentifier:#"loadedData" sender:self];
}
Inside the second UIViewController I did the same also with 0.5 seconds delay. After changing the delay to a higher value, it worked fine. It's like the segue can't be performed too fast after another segue.
I had the same problem when I need to Present My Login View Controller from another View Controller If the the User is't authorized, I did it in ViewDidLoad Method of my Another View Controller ( if not authorized -> presentModalViewController ). When I start to make it in ViewDidAppear method, I solved this problem. I Think that ViewDidLoad only initialize properties and after that the actual showing view algorithm begins! Thats why you must use viewDidAppear method to make modal transitions!
If you're using transitioningDelegate (not the case in this question's example), also set modalPresentationStyle to .Custom.
Swift
let vc = storyboard.instantiateViewControllerWithIdentifier("...")
vc.transitioningDelegate = self
vc.modalPresentationStyle = .Custom
I had this problem because of a typo:
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
instead of
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
It was calling "WillAppear" in the super instead of "DidAppear"
I had lot of problem with the same issue. I solved this one by
Initiating the ViewController using the storyboad instantiateViewControllerWithIdentifier method. i.e Intro *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"introVC"];
[self.tabBarController presentModalViewController : vc animated:YES];
I have the viewcontroller in my storyboard, for some reason using only [[introvc alloc] init]; did not work for me.
I solved it by writing
[self.navigationController presentViewController:viewController
animated:TRUE
completion:NULL];
I had this problem with a third party code. Someone forgot to set the super inside of viewWillAppear and viewWillDisappear in a custom TabBarController class.
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// code...
}
or
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// code...
}
I had the same error. I have a tab bar with 3 items and I was unconsciously trying to call the root view controller of item 1 in the item 2 of my tab bar using performSegueWithIdentifier.
What happens is that it calls the view controller and goes back to the root view controller of item 2 after a few seconds and logs that error.
Apparently, you cannot call the root view controller of an item to another item.
So instead of performSegueWithIdentifier
I used [self.parentViewController.tabBarController setSelectedIndex:0];
Hope this helps someone.
I had the same problem and thought I would post in case someone else runs into something similar.
In my case, I had attached a long press gesture recognizer to my UITableViewController.
UILongPressGestureRecognizer *longPressGesture = [[[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(onLongPress:)]
autorelease];
[longPressGesture setMinimumPressDuration:1];
[self.tableView addGestureRecognizer:longPressGesture];
In my onLongPress selector, I launched my next view controller.
- (IBAction)onLongPress:(id)sender {
SomeViewController* page = [[SomeViewController alloc] initWithNibName:#"SomeViewController" bundle:nil];
[self.navigationController pushViewController:page animated:YES];
[page release];
}
In my case, I received the error message because the long press recognizer fired more than one time and as a result, my "SomeViewController" was pushed onto the stack multiple times.
The solution was to add a boolean to indicate when the SomeViewController had been pushed onto the stack. When my UITableViewController's viewWillAppear method was called, I set the boolean back to NO.
I found that, if you are using a storyboard, you will want to put the code that is presenting the new view controller in viewDidAppear. It will also get rid of the "Presenting view controllers on detached view controllers is discouraged" warning.
In Swift 2+ for me works:
I have UITabBarViewController in storyboard and I had selectedIndex property like this:
But I delete it, and add in my viewDidLoad method of my initial class, like this:
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.selectedIndex = 2
}
I hope I can help someone.
This error will be displayed when trying to present an UINavigationController that is lazily initialized via a closure.
Actually you need to wait till the push animation ends. So you can delegate UINavigationController and prevent pushing till the animation ends.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
waitNavigation = NO;
}
-(void)showGScreen:(id)gvc{
if (!waitNavigation) {
waitNavigation = YES;
[_nav popToRootViewControllerAnimated:NO];
[_nav pushViewController:gvc animated:YES];
}
}
As #danh suggested, my issue was that I was presenting the modal vc before the UITabBarController was ready. However, I felt uncomfortable relying on a fixed delay before presenting the view controller (from my testing, I needed to use a 0.05-0.1s delay in performSelector:withDelay:). My solution is to add a block that gets called on UITabBarController's viewDidAppear: method:
PRTabBarController.h:
#interface PRTabBarController : UITabBarController
#property (nonatomic, copy) void (^viewDidAppearBlock)(BOOL animated);
#end
PRTabBarController.m:
#import "PRTabBarController.h"
#implementation PRTabBarController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.viewDidAppearBlock) {
self.viewDidAppearBlock(animated);
}
}
#end
Now in application:didFinishLaunchingWithOptions:
PRTabBarController *tabBarController = [[PRTabBarController alloc] init];
// UIWindow initialization, etc.
__weak typeof(tabBarController) weakTabBarController = tabBarController;
tabBarController.viewDidAppearBlock = ^(BOOL animated) {
MyViewController *viewController = [MyViewController new];
viewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[weakTabBarController.tabBarController presentViewController:navigationController animated:NO completion:nil];
weakTabBarController.viewDidAppearBlock = nil;
};
you need make sure -(void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated and -(void)endAppearanceTransition is create together in the class.
I had the same issue. When developing I wanted to bypass screens. I was navigating from one view controller to another in viewDidLoad by calling a selector method.
The issue is that we should let the ViewController finish transitioning before transitioning to another ViewController.
This solved my problem: The delay is necessary to allow ViewControllers finish transitioning before transitioning to another.
self.perform(#selector(YOUR SELECTOR METHOD), with: self, afterDelay: 0.5)
For me this error occurred because i didn't have UIWindow declared in the upper level of my class when setting a root view controller
rootViewController?.showTimeoutAlert = showTimeOut
let navigationController = SwipeNavigationController(rootViewController: rootViewController!)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
Ex if I tried declaring window in that block of code instead of referencing self then I would receive the error
I had this problem when I had navigated from root TVC to TVC A then to TVC B. After tapping the "load" button in TVC B I wanted to jump straight back to the root TVC (no need to revisit TVC A so why do it). I had:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:YES];
//Pop self to return to root
[self.navigationController popViewControllerAnimated:YES];
...which gave the error "Unbalanced calls to begin/end etc". The following fixed the error, but no animation:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root
[self.navigationController popViewControllerAnimated:NO];
This was my final solution, no error and still animated:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root, only works if first pop above is *not* animated
[self.navigationController popViewControllerAnimated:YES];
I encountered this error when I hooked a UIButton to a storyboard segue action (in IB) but later decided to have the button programatically call performSegueWithIdentifier forgetting to remove the first one from IB.
In essence it performed the segue call twice, gave this error and actually pushed my view twice. The fix was to remove one of the segue calls.
Hope this helps someone as tired as me!
Swift 5
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Delete or comment the below lines on your SceneDelegate.
// guard let windowScene = (scene as? UIWindowScene) else { return }
// window?.windowScene = windowScene
// window?.makeKeyAndVisible()
let viewController = ListVC()
let navViewController = UINavigationController(rootViewController: viewController)
window?.rootViewController = navViewController
}