I have issue to remove ViewController from Navigation stack? - ios

I have 5 VC's, I'm successfully removing ViewController from navigation stack. But the problem is when click back button on navigation, it's moving into previous VC and it's showing removed VC on navigation bar.
Ex: I have 5 VC's: VC1, VC2, VC3, VC4, VC5.
Now I'm navigating from VC1 -> VC2, ..... VC4 -> VC5. And I have custom navigation bar back button title. Here I'm removing VC4 from stack.
When click back button in VC5 it's directly moving into VC3. But navigation bar is VC4. When click navigation bar once again now it's displaying VC3 navigation bar in same VC.
HOW TO resolve this issue. I want to display directly VC3 and vc3 navigation bar in single click.
Code to remove VC from Navigation stack:
guard let navigationController = self.navigationController else { return }
var navigationArray = navigationController.viewControllers // To get all UIViewController stack as Array
navigationArray.remove(at: navigationArray.count - 2) // To remove previous UIViewController
self.navigationController?.viewControllers = navigationArray

Use the following:
navigationController?.setViewControllers(navigationArray!, animated: true)
E.g.
guard let navigationController = self.navigationController else { return }
var navigationArray = navigationController.viewControllers
navigationArray.remove(at: navigationArray.count - 2)
navigationController.setViewControllers(navigationArray!, animated: true)
From the docs:
Use this method to update or replace the current view controller stack
without pushing or popping each controller explicitly. In addition,
this method lets you update the set of controllers without animating
the changes, which might be appropriate at launch time when you want
to return the navigation controller to a previous state.
If animations are enabled, this method decides which type of
transition to perform based on whether the last item in the items
array is already in the navigation stack. If the view controller is
currently in the stack, but is not the topmost item, this method uses
a pop transition; if it is the topmost item, no transition is
performed. If the view controller is not on the stack, this method
uses a push transition. Only one transition is performed, but when
that transition finishes, the entire contents of the stack are
replaced with the new view controllers. For example, if controllers A,
B, and C are on the stack and you set controllers D, A, and B, this
method uses a pop transition and the resulting stack contains the
controllers D, A, and B.
Edit 1
When you are pushing VC5, use the following code
let vc = YourVC5()
var array = navigationController?.viewControllers
array?.removeLast()
array?.append(vc)
navigationController?.setViewControllers(array!, animated: true)
The idea is when you push VC5 into stack, before pushing we are excluding VC4 from the list thus it will have VC3 beneath VC5 by default and you just need to call the navigationController?.popViewController(animated: true) and it should pop directly to VC3

Hide default back button and add custom back button with action:
override func viewDidLoad {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let customBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItem.Style.plain, target: self, action: #selector(back))
self.navigationItem.leftBarButtonItem = customBackButton
}
Use popToViewController to move back to specific viewcontroller:
#objc func back(sender: UIBarButtonItem) {
guard let navigationController = self.navigationController else { return }
var navigationArray = navigationController.viewControllers // To get all
self.navigationController!.popToViewController(navigationArray[navigationArray.count - 2], animated: true)
}

if you are using custom NavigationBar than you need to use custom back button click Action in VC5 :-
#IBAction func btnBackAction(_ sender: UIButton) {
let vc = VC3()
self.navigationController.popToViewController(vc, animated: true)
}
And if you can use Default NavigationBar than need to remove VC4 in navigation stack in VC5 like this:-
guard let navigationController = self.navigationController else { return }
var navigationArray = navigationController.viewControllers // To get all UIViewController stack as Array
navigationArray.remove(at: navigationArray.count - 2) // To remove previous UIViewController
self.navigationController?.viewControllers = navigationArray

You can use popToViewController(_:animated:) (as Prakash Shaiva answered above):
guard let navigationController = self.navigationController else { return }
var navigationArray = navigationController.viewControllers // To get all
self.navigationController.popToViewController(navigationArray[navigationArray.count - 2], animated: true)
And try to update your NavigationBar in the method viewWillAppear(_:) for VC3.

Related

How to 'pop' a navigation controller and all of its view controllers

I have inherited a navigation controller issue in an existing app that I'm trying to solve cleanly.
This app has multiple storyboards and multiple UINavigationControllers. At one point in the app, a series of view controllers is presented modally, using a separate storyboard and a separate nav controller. When the modal process is complete, the navigation hierarchy looks something like this:
NavController1 -> VC1 ['Present Modally' segue] -> NavController2 -> VC2 -> VC3 -> VC4
When the user completes the modal activity in VC4, dismiss() is called programmatically on VC4 and the user can then navigate back to VC1 using the back button.
However, what we really need to do is to 'pop off' all of the modally presented set of view controllers (and their nav controller) when the user finishes the modal activity. The problem is that from VC3 or VC4 I can't call popToRootViewController(). I also can't traverse down the VC stack to find VC1, since the current Nav controller doesn't manage it.
A couple solutions come to mind:
1) use the notification manager and have VC1 listen for the message to pop everything off back to itself
2) pass a reference to VC1 as a delegate all the way up the chain so that VC3 or 4 can have it pop everything off
Both of these solutions follow the general maxim that the presenting VC should be the one that dismisses, but neither are what I would consider clean.
I would welcome any thoughts or alternative solutions.
Assuming that these are the way the view controllers were laid out:
NavController1 --['Root View Controller' segue]--> VC1 --['Present Modally' segue]--
--> NavController2 --['Root View Controller' segue]--> VC2 --['Push' segue]--> VC3 --['Push' segue]--> VC4
You should be able to go back to VC1 by dismissing either VC2, VC3 or VC4.
// example for vc4
vc4.navigationController?.dismiss(animated: true, completion: nil)
However, if each of the viewControllers were presented modally, you should be able to traverse through the presentingViewController to reach VC1.
var currentVC: UIViewController? = self
var presentingVC: UIViewController? = currentVC?.presentingViewController
while presentingVC != nil && !(presentingVC is VC1) {
currentVC?.dismiss(animated: true, completion: nil)
currentVC = presentingVC
presentingVC = currentVC?.presentingViewController
}
Hope that helps.
When popping, you may kick out the viewControllers from your navigation Controller, would solve your problem
extension UINavigationController {
public func removeViewController(classes : [String]) {
var vcs = [UIViewControllers]()
for viewController in self.viewControllers {
let name = viewController.className
if !classes.contains(name) {
vcs.append(viewController)
}
}
if classes.count < vcs.count {
self.viewControllers = vcs
}
}
}
now think you have 4 viewControllers , A, B, C, D, you want to remove B and C and Move Back To A
In D's View Controller
override func viewDidLoad() {
super.viewDidLoad()
//your works
let viewControllersToRemove = [String(describing: type(of:B)), String(describing: type(of:C))]
navigationController.removeViewControoler(classes : viewControllersToRemove)
}

How can I set back to first view controller by navigation controller in code in present without segue?

I have in my project tow view controller and I linked the first VC with navigation controller but the problem is : I used present to go to second VC (that mean I didn't use segue) ... how can I set back to first VC in navigation Controller by code (without segue) .
picture from my storyboard
my code :
let storyboard = self.storyboard
let viewcontroller = storyboard?.instantiateViewController(withIdentifier: "contact_detail") as! ViewController2
viewcontroller.arr2 = arr
present(viewcontroller, animated: true, completion: nil)
With the dismiss method it will work.
Dismisses the view controller that was presented modally by the view controller. (Apple Docs)
self.dismiss(animated: true, completion: nil)
If you are presenting a viewcontroller with present method, you can dismiss it with dismiss method.
If you are adding any view controller with push method then only it will get added to your navigation stack and you can remove it by calling popviewcontroller on it's back action.
Here, you are presenting a viewcontroller, hence it will not get added to your first navigation stack and you can not remove it with pop action on click of back.
If you are looking for a back button like feature on presented view controller, you can add a back button in toolbar, dismiss a viewcontroller on back button action, and can animate it like a popnavigation while dismissing.
extension UINavigationController {
public func removeViewController(classes : [String]) {
var vcs = [UIViewControllers]()
for viewController in self.viewControllers {
let name = viewController.className
if !classes.contains(name) {
vcs.append(viewController)
}
}
if classes.count < vcs.count {
self.viewControllers = vcs
}
}
}
now think you have 4 viewControllers , A, B, C, D, you want to remove B and C and Move Back To A
In D's View Controller
override func viewDidLoad() {
super.viewDidLoad()
//your works
let viewControllersToRemove = [String(describing: type(of:B)), String(describing: type(of:C))]
navigationController.removeViewControoler(classes : viewControllersToRemove)
}
I solved this by using pushViewController
self.navigationController?.pushViewController(MyViewController, animated: true)

Navigation pushViewController is not working

i have login view controller. when usersingin i' showing popup for that i
refereed
https://www.youtube.com/watch?v=S5i8n_bqblE i can achieved that.
popup had button when i click the button navigation to next view controller
but its now working when i am clicking that action is performing but its not navigating to next view controller wher i did mistake
Singin
class DigitalGateViewController: NANavigationViewController
{
#IBAction func singin(_ sender: UIButton)
{
let lv = NAViewPresenter().activityVC()
self.present(lv, animated: true)
}
}
this is popupviewcontroller
class ActivityViewController: NANavigationViewController {
#IBAction func okbuttonclick() {
let dv = NAViewPresenter().myGuestListVC()
// self.navigationController?.pushViewController(dv, animated: true)
}
}
its not push to textview controller in swift
When you present a view controller, its presented modally and is not pushed onto the previous navigation controller's stack. Hence, you tried to call self.navigationController?.pushViewController(), it doesn't work, because self i.e. NAViewPresenter().myGuestListVC() isn't embedded in a navigation Controller.
If you want to push the new VC onto the previous stack, you will have to dismiss the presented pop up and then push. The easiest way to do this is to use a delegate method.
Edit:
if you want to create a new navigationController, you can do something like this :
let navController = UINavigationController(rootViewController: NAViewPresenter().myGuestListVC())
present(navController, animated: true)
After presenting the navController, you can use self.navigationController.push method henceforth.
The reason why its not pushing because you are presenting it modally not pushing on the navigation stack and so it wont have any navigationController. If you want to push from your modal popup, you can access the property presentingViewController on your modal object and try to push it on navigationController from there.
self.presentingViewController?.navigationController?.pushViewController(myVC, animated: true)
dismiss(animated: true, completion: nil)

Back button in Navigation bar not displayed

I have a view controller connected to a popover controller, which directs to a new view controller. I want to add a back button on the navigation item to the last view controller so that when I click back it will return to the first view controller
I tried creating segue and click action in the popover controller and add back button in the segue function/click action function
neither of them works
This is what I do in the Popover. I already present the popover with UIPopoverPresentationControllerDelegate and this is what PopoverVC looks like. In this way, I add IBAction to enable the click action of the label in popover. I write the backbutton in the IBAction function and create the navigationcontroller with rootview, and the back button doesn't appear.
class PopoverViewController: UIViewController {
//TODO: add BACK button
#IBAction func createNewChat(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle:nil)
let contactVC = storyboard.instantiateViewController(withIdentifier: "contacts") as? NewContactsViewController
let nc2 = UINavigationController()
let back = UIBarButtonItem()
back.title = "Back"
nc2.navigationItem.backBarButtonItem = back
nc2.pushViewController(contactVC!, animated: true)
self.present(nc2, animated: true, completion: nil)
}
}
I also tried another way, I create a segue in storyboard and connect the popover to the navigation controller, the backbutton still doesn't appear.The segue is hooked up to the navigation controller in the storyboard.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let seg = segue.destination as! ContactsTableViewController
let back = UIBarButtonItem()
back.title = "Back"
navigationItem.backBarButtonItem = back
}
My question is: how to make the back button appear? After it appears, how to make it work? I don't want the back button points to the popover, I want the back button will make users return to the VC that creates the popoverVC.
The back button in a navigation bar doesn't belong to THIS view controller; it is the back button item of the previous view controller already on the navigation stack. But you have no previous view controller. So just use, like, a left bar button item that says Back, or something.
Thus, give the bar button item (back) an action and target and change
nc2.navigationItem.backBarButtonItem = back
to
nc2.navigationItem.leftBarButtonItem = back

Navigation bar not appearing after delegation setup

I have a UILabel in my ViewController that has a NavigationController (let's say view controller A) with a tap gesture recognizer attached to the label. When the label is tapped another view appears (let's call it B). The user picks some text in B and the view dismisses back to A with the label text updated with the selection. So I created a delegation between A and B to get the selection. The problem is that I do not see the NavigationBar when B appears. Is there a way to fix this?
ViewController A
#IBOutlet weak var sectionName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let sectionLabelTap = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:)))
sectionName.isUserInteractionEnabled = true
sectionName.addGestureRecognizer(sectionLabelTap)
}
#objc func labelTapped(_ sender: UITapGestureRecognizer) {
let sectionNameVC = storyboard?.instantiateViewController(withIdentifier: "SectionName") as! SectionNameTableViewController
sectionNameVC.selectionNameDelegate = self
sectionNameVC.userData = userData
present(sectionNameVC, animated: true, completion: nil)
}
In order to display the Navigation bar the UIViewController needs to have a UINavigationController.
You can add that sectionNameVC ViewController into a UINavigationController to persevere the present animation.
In that case your code might look something like this:
#objc func labelTapped(_ sender: UITapGestureRecognizer) {
let sectionNameVC = storyboard?.instantiateViewController(withIdentifier: "SectionName") as! SectionNameTableViewController
sectionNameVC.selectionNameDelegate = self
sectionNameVC.userData = userData
let naviagtionController = UINavigationController(rootViewController: sectionNameVC)
present(naviagtionController, animated: true, completion: nil)
}
Or you can simply call pushViewController on the View Controller A's navigation Controller, like this:
self.navigationController?.pushViewController(sectionNameVC, animated: true)
This will add sectionNameVC into the View Controller A's navigation Controller stack. In this case the transition animation will be different, the sectionNameVC will come from your right.
You are missing the concept between "Presenting" View Controller & "Navigating" the View Controller. You will get the answer, once you understood the concept. Here, it is..
When you are presenting the ViewController, you are completely replacing the stack container to the new view controller.
STACK holds the addresses of the ViewControllers you push or pop via navigating.
e.g:
present(sectionNameVC, animated: true, completion: nil)
On the other hand, if you are navigating to other view controller by pushing it. In this case, you can go back to previous controller by simple popping the ViewController address from stack.
e.g:
self.navigationController?.pushViewController(sectionNameVC, animated: true)
self.navigationController?.popViewController(animated: true)
So, If you navigate then, only you will get navigation Bar.
Now, in your case, you are presenting the ViewController and hence, navigation bar is not showing.

Resources