Push a viewcontroller if it is not present in the navigation stack - swift

I want to check whether a viewcontroller is present in a navigation stack or not. If it is present, I need to pop it otherwise I need to push it to the navigation stack. I have tried the following code. If it is not present, control is transfering to the else block but I'm unable to navigate to the screen. Please help me
for aViewController in viewControllers! {
if aViewController is TabProfileViewController {
self.navigationController?.popToViewController(aViewController, animated: true)
}
else {
let lvc = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController
self.navigationController?.pushViewController(lvc!, animated: true)
}
}

You're checking it in every loop, so if one time the first condition is true maybe it can become false in next iteration, so it will pop and push. try following code:
if let viewController = viewControllers?.first(where: { $0 is TabProfileViewController }) {
navigationController?.popToViewController(viewController, animated: true)
} else {
let lvc = storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController
navigationController?.pushViewController(lvc!, animated: true)
}

Hope this code is work for you.
if navigationController != nil && !(navigationController?.topViewController is YOURCONTROLLER) {
for aViewController in (navigationController?.viewControllers)!
{
if aViewController is YOURCONTROLLER {
//Your controller found
}else{
//push using navigation
}
}
}

This is Another way to do that:
if arrViewController != nil && !(arrViewController?.topViewController is TabProfileViewController) {
for aViewController in (arrViewController?.viewControllers)! {
if aViewController is Dashboard {
_ = self.navigationController?.popToViewController(aViewController, animated: true)
break
}
}
}else{
let lvc = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController
self.navigationController?.pushViewController(lvc!, animated: true)
}

Related

Swift segue not doing what it is supposed to do

I am having a little problem with a segue in my application.
When I try to push a segue so that it has a navbar it shows up correctly in the storyboard but not when I try it on my iPhone.
This is an overview of a couple of view controllers where my problem lays.
This is supposed to be the segue, so you can see that it has a navigation bar and is correctly positioned on the storyboard.
This is the view on the iPhone. No navigation bar or nothing. I tried everything but can't seem to find a solution to this problem.
Does anyone what the problem could be?
A little extra side information:
I don't know if may have something to do with the problem but the navigation view controller is not always present only when the user is logged in the app. this is decided on a log in screen if the user is not logged in the user will see a normal login screen. Else it will go to navigation view controller with a view did appear function and self.present.
Here is the code that handles that action.
// Sees if the user is logged, If yes --> go to the account detail page else go to the account view.
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let data = UserDefaults.standard.data(forKey: "User") {
do {
// Create JSON Decoder
let decoder = JSONDecoder()
// Decode Note
_ = try decoder.decode(User.self, from: data)
guard let loginVC = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier:
"AccountDetailViewController") as? AccountDetailViewController else { return }
loginVC.modalPresentationStyle = .overCurrentContext
self.present(loginVC, animated: false, completion: {})
} catch {
print("Unable to Decode Note (\(error))")
}
}
}
You should push view controller instead of present. Please check this article to know more about Pushing, Popping, Presenting, & Dismissing ViewControllers
You can push AccountDetailViewController without segues. And you don't need to call performSegue(withIdentifier:) into tableView's didSelect function.
Remove segue from Interface Builder
let navigator = UINavigationController()
guard let loginVC = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier:
"AccountDetailViewController") as? AccountDetailViewController else { return }
loginVC.modalPresentationStyle = .overCurrentContext
navigator.pushViewController(loginVC, animated: true)
After succesful login, you are presenting AccountDetailViewController without adding it in a navigation controller. I would suggest you to use these extensions that i created.
extension UIViewController {
func pushVC(vcName : String) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
func pushVC(storyboardName : String, vcName : String) {
let vc = UIStoryboard.init(name: storyboardName, bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
func popVC() {
self.navigationController?.popViewController(animated: true)
}
func makeRootVC(storyBoardName : String, vcName : String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let vc = UIStoryboard(name: storyBoardName, bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
let nav = UINavigationController(rootViewController: vc)
nav.navigationBar.isHidden = true
appDelegate.window?.rootViewController = nav // If using XCode 11 and above, copy var window : UIWindow? in your appDelegate file
let options: UIView.AnimationOptions = .transitionCrossDissolve
let duration: TimeInterval = 0.6
UIView.transition(with: appDelegate.window!, duration: duration, options: options, animations: {}, completion: nil)
}
}
Now in your case, when a user logs in, you should change your root view controller to AccountDetailViewController. So first, copy paste the above extension anywhere in your file and then use it like this:
// Sees if the user is logged, If yes --> go to the account detail page else go to the account view.
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let data = UserDefaults.standard.data(forKey: "User") {
do {
// Create JSON Decoder
let decoder = JSONDecoder()
// Decode Note
_ = try decoder.decode(User.self, from: data)
self.makeRootVC(storyBoardName : "Main", vcName :"AccountDetailViewController")
} catch {
print("Unable to Decode Note (\(error))")
}
}
}

How to check after popping a ViewController, the ViewController on top of the stack, is a particular one?

Suppose I am currently on BaseViewController . I can approach this view controller class from 2 other view controllers, say A and B. So when I pop BaseViewController how do I check if A or B is present on top of the stack? Here is my code below(inside BaseViewController):
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
// Id after popping viewController is B do this
// Instantiate B and push it
}
There are many methods to do this
First and easiest method
func visibleViewController() -> UIViewController? {
let appDelegate = UIApplication.shared.delegate
if let window = appDelegate!.window {
return window?.visibleViewController
}
return nil
}
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
if visibleViewController() == viewControllerA{
//Do stuff for A
}else{
//Do stuff for B
}
}
Second and obvious method
let topController = UIApplication.sharedApplication().keyWindow?.rootViewController
if topController == viewControllerA {
//Do stuff for A
}else{
//Do stuff for B
}
You can pop and push view controller at the same time.
not tested:
navigationController?.viewControllers.removeLast()
let topVC = navigationController?.viewControllers.last
if topVC == A {
navigationController?.viewControllers.append(A)
navigationController?.setViewControllers(A, animated: false)
} else {
navigationController?.viewControllers.append(B)
navigationController?.setViewControllers(B, animated: false)
}
Inside BaseViewController class:
func goToAOrBViewController() {
guard let navigationController = self.navigationController else { return }
navigationController.popViewController(animated: false)
guard let viewController = navigationController.topViewController else { return }
if viewController is ViewControllerA {
// Do something for A
} else if viewController is ViewControllerB {
// Do Something for B
}
// You need not put else-if case if you are sure BaseVC is reachable from A and B only (just use else)
}

Get back parentViewController

I have a PhoneViewController : UIViewController
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
I am presenting it from 2 different controllers.
// UIViewController1
self.navigationController?.pushViewController(phonePage, animated: true)
// UIViewController2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
I would like to have a have to detect which controller was its parent. How would I be able to do that?
you can try this code:-
if let wd = self.window {
var vc = wd.rootViewController
if(vc is UINavigationController){
vc = (vc as UINavigationController).visibleViewController
}
if(vc is YourViewController){
//your code
}
}
The short answer is :
if let navController = self.navigationController {
return navController.viewControllers[navController.viewControllers.count - 1]
// take care if count <= 1
else {
return self.parent
}
But is this what you really looking for ? What behavior are you trying to implement based on who is his parent ?
I don't have the answer to this question but you should make your code readable. Let me explain by an example :
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
// Option 1
self.navigationController?.pushViewController(phonePage, animated: true)
phonePage.mode = PhonePageMode.list
// Option 2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
phonePage.mode = PhonePageMode.grid
// In your PhoneViewController class
switch self.mode {
case .list: // present as a list
case .grid: // present as a grid
}
is more readable than :
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
// Option 1
self.navigationController?.pushViewController(phonePage, animated: true)
// Option 2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
// In your PhoneViewController class
guard let parent = self.parentViewController else { return }
if parent is ThisClassWhichWantsAList {
// present as list
} else if parent is ThisOtherClassWhichWantsAGrid {
// present as grid
}
If you want to use its parent as a condition to do things differently, you'd rather use an additional attribute. Your futur self will be thankful.
if your'r view controller is pushed from navigationViewController than parent class in parentViewController available and if presented than in presentationController available.
You can check in this way:
if let parentVC = self.navigationController.parentViewController{
//parentVC.className
}
if let presentedVC = self.navigationController?.presentationController{
//presentedVC.className
}
So you basically have to check if the view controller in the navigation controller stack, at the index of count - 1 is the kind of view controller that you are looking for.
Short version:
if navigationController.viewControllers[navigationController.viewControllers.count - 1].isKind(of: TheVCYouAreLooking for.self) {
print("it is")
} else {
print("it is NOT")
}
Long version from a playground:
//These are your two view controllers
class FirstVCClass: UIViewController {}
class SecondVCClass: UIViewController {}
let vc = FirstVCClass()
let secondVC = SecondVCClass()
//Create a navigationcontroller and add the first VC in the stack
let navigationController = UINavigationController()
navigationController.setViewControllers([vc], animated: true)
//Now push the secondVC
vc.navigationController?.pushViewController(secondVC, animated: true)
//The last vc in the stack is the one you've just pushed
print(navigationController.viewControllers.last!)
// Now check
if navigationController.viewControllers[navigationController.viewControllers.count - 1].isKind(of: FirstVCClass.self) {
//The view controller that is before you current vc in the stack is of the class
print("it is")
} else {
print("it is NOT")
}

presentViewController has no navigationController swift

My transition to the next view is like this:
if let navigationController = navigationController {
if let storyboard:UIStoryboard = UIStoryboard(name: "myStoryboard", bundle: nil) {
if let vc = storyboard.instantiateViewControllerWithIdentifier("myViewController") as? MyViewController {
dispatch_async(dispatch_get_main_queue()) {
navigationController.presentViewController(vc, animated: true, completion: nil)
}
}
}
}
This works fine. I want this kind of transition. But when I call following code in MyViewController, the NavigationController is nil:
if let navigationController = navigationController {
print("yeah i have a nc")
} else {
print("its nil") //this will call
}
When I use navigationController.pushViewController(vc, animated: true)
everything works fine. But I really want the transition. Is this a wrong implementation on my side or is presentViewController always without a navigationController? If yes, what can I do?
My Controller A is already embedded in a navigationController. I use navigationController.presentViewController to go to MyViewController. And from MyViewController I want to push to a next ViewController C.
SOLUTION THAT WORKED FOR ME
I don't know why, but when you use the presentViewController you have to define a new(?) root for your navigationController.
In this context I understood Ahmad Fs answer.
if let storyboard:UIStoryboard = UIStoryboard(name: "myStoryboard", bundle: nil) {
if let vc = storyboard.instantiateViewControllerWithIdentifier("MyViewController") as? MyViewController {
if let navController:UINavigationController = UINavigationController(rootViewController: vc) {
dispatch_async(dispatch_get_main_queue()) {
self.presentViewController(navController, animated:true, completion: nil)
}
}
}
}
SWIFT 3
let storyboard = UIStoryboard(name: UIConstants.Storyboards.registration, bundle: nil)
if let vc = storyboard.instantiateViewController(withIdentifier: "YourViewControllerIdentifier") as? YourViewController {
let navigationController = UINavigationController(rootViewController: vc)
DispatchQueue.main.async {
navigationController.present(vc, animated: true)
}
}
I found "my" solution here

Back when current view controller is not presented by segue. Swift

I am trying to present VC2 from VC1 without using segue. It works. Then, I tried to use self.navigationController?.popViewControllerAnimated(true) to back but it does not work. I am wondering what is the code I should use to back from VC2 to VC1. Below code is in appDelegate.
AppDelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let VC2 = storyboard.instantiateViewControllerWithIdentifier("VC2") as! VC2
let navController = UINavigationController(rootViewController: VC2)
self.topViewController()!.presentViewController(navController, animated: false, completion: nil)
func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let MMDrawers = base as? MMDrawerController {
for MMDrawer in MMDrawers.childViewControllers {
return topViewController(MMDrawer)
}
}
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
VC2
#IBAction func backButtonTapped(sender: AnyObject) {
print(self.navigationController?.viewControllers) // print([<MyAppName.VC2: 0x12f147200>])
self.navigationController?.popViewControllerAnimated(true)
}
Here is the Flow of your navigation :
Current Screen - Presenting A New Screen (Which itself embed within a navigation controller with vc2) so Popviewcontroller won't work .
If you present any viewcontroller then popviewcontroller wont work rather use dismissviewcontroller to come out previous screen .
Use This :
self.dismissViewControllerAnimated(true, completion: {})
Solved!
Thanks.