Programmatically go back to previous ViewController in Swift - ios

I send the user over to a page on a button click. This page is a UITableViewController.
Now if the user taps on a cell, I would like to push him back to the previous page.
I thought about something like self.performSegue("back").... but this seems to be a bad idea.
What is the correct way to do it?

Swift 3:
If you want to go back to the previous view controller
_ = navigationController?.popViewController(animated: true)
If you want to go back to the root view controller
_ = navigationController?.popToRootViewController(animated: true)
If you are not using a navigation controller then pls use the below code.
self.dismiss(animated: true, completion: nil)
animation value you can set according to your requirement.

Swift 3, Swift 4
if movetoroot {
navigationController?.popToRootViewController(animated: true)
} else {
navigationController?.popViewController(animated: true)
}
navigationController is optional because there might not be one.

Swift 3
I might be late in the answer but for swift 3 you can do it this way:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "< Back", style: .plain, target: self, action: #selector(backAction))
// Do any additional setup if required.
}
func backAction(){
//print("Back Button Clicked")
dismiss(animated: true, completion: nil)
}

Swift 4
there's two ways to return/back to the previous ViewController :
First case : if you used : self.navigationController?.pushViewController(yourViewController, animated: true)
in this case you need to use self.navigationController?.popViewController(animated: true)
Second case : if you used : self.present(yourViewController, animated: true, completion: nil)
in this case you need to use self.dismiss(animated: true, completion: nil)
In the first case , be sure that you embedded your ViewController to a navigationController in your storyboard

swift 5 and above
case 1 : using with Navigation controller
Back to the previous view controller
self.navigationController?.popViewController(animated: true)
Back to the root view controller
self.navigationController?.popToRootViewController(animated: true)
case 2 : using with present view controller
self.dismiss(animated: true, completion: nil)

In the case where you presented a UIViewController from within a UIViewController i.e...
// Main View Controller
self.present(otherViewController, animated: true)
Simply call the dismiss function:
// Other View Controller
self.dismiss(animated: true)

If Segue is Kind of 'Show' or 'Push' then You can invoke "popViewController(animated: Bool)" on Instance of UINavigationController. Or if segue is kind of "present" then call "dismiss(animated: Bool, completion: (() -> Void)?)" with instance of UIViewController

for swift 3
you just need to write the following line of code
_ = navigationController?.popViewController(animated: true)

This one works for me (Swift UI)
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("This is the detail view")
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Back")
}
}
}
}

Try this:
for the previous view use this:
navigationController?.popViewController(animated: true)
pop to root use this code:
navigationController?.popToRootViewController(animated: true)

Swift 4.0 Xcode 10.0 with a TabViewController as last view
If your last ViewController is embebed in a TabViewController the below code will send you to the root...
navigationController?.popToRootViewController(animated: true)
navigationController?.popViewController(animated: true)
But If you really want to go back to the last view (That could be Tab1, Tab2 or Tab3 view..)you have to write the below code:
_ = self.navigationController?.popViewController(animated: true)
This works for me, i was using a view after one of my TabView :)

For questions regarding how to embed your viewController to a navigationController in the storyboard:
Open your storyboard where your different viewController are located
Tap the viewController you would like your navigation controller to start from
On the top of Xcode, tap "Editor"
-> Tap embed in
-> Tap "Navigation Controller

I would like to suggest another approach to this problem. Instead of using the navigation controller to pop a view controller, use unwind segues. This solution has a few, but really important, advantages:
The origin controller can go back to any other destination controller (not just the previous one) without knowing anything about the destination.
Push and pop segues are defined in storyboard, so no navigation code in your view controllers.
You can find more details in Unwind Segues Step-by-Step. The how to is better explained in the former link, including how to send data back, but here I will make a brief explanation.
1) Go to the destination (not the origin) view controller and add an unwind segue:
#IBAction func unwindToContact(_ unwindSegue: UIStoryboardSegue) {
//let sourceViewController = unwindSegue.source
// Use data from the view controller which initiated the unwind segue
}
2) CTRL drag from the view controller itself to the exit icon in the origin view controller:
3) Select the unwind function you just created a few moments ago:
4) Select the unwind segue and give it a name:
5) Go to any place of the origin view controller and call the unwind segue:
performSegue(withIdentifier: "unwindToContact", sender: self)
I have found this approach payoffs a lot when your navigation starts to get complicated.
I hope this helps someone.

I did it like this
func showAlert() {
let alert = UIAlertController(title: "Thanks!", message: "We'll get back to you as soon as posible.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
self.dismissView()
}))
self.present(alert, animated: true)
}
func dismissView() {
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
}

If you want to close previous two view controllers just call popViewController two times like this way:
self.navigationController?.popViewController(animated: true)
self.navigationController?.popViewController(animated: true)

I can redirect to root page by writing code in "viewDidDisappear" of navigated controller,
override func viewDidDisappear(_ animated: Bool) {
self.navigationController?.popToRootViewController(animated: true)
}

Related

Using instance variable to indicate the view is Edit or Show

In Start Developing iOS Apps (Swift): Implement Edit and Delete Behavior
, The offical tutorial tell me should use presentingViewController and navigationController to indicate the specified view is Edit or Show, Like following code:
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddMealMode = presentingViewController is UINavigationController
if isPresentingInAddMealMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The MealViewController is not inside a navigation controller.")
}
}
The adding view is presented by modal, the editing view is presented by embed navigation controller, But I think this approach is not good to understand and easy maintain, How about introduce a isEditOrShow instance varible in the view to indicate the state? like following:
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
if isEditingOrShow = .edit{
dismiss(animated: true, completion: nil)
}
else isEditingOrShow = .show{
owningNavigationController.popViewController(animated: true)
}
}
The tutorial explains that, there are two ways in which you can dismiss view controllers.
For example, when you present a view controller as modal, you can use the below code to dismiss it.
dismiss(animated: true, completion: nil)
However, if you are using a push presentation (Navigation controller), then you should use the below code to dismiss it.
owningNavigationController.popViewController(animated: true)
How about introduce a bool isEditOrShow instance varible in the view to indicate the state?
From what I understand, you won't be needing isEditOrShow variable. If you have any questions, let me know.

How to dismiss 2 modal view controllers without weird animation [duplicate]

This question already has answers here:
Dismissing multiple modal view controllers at once?
(7 answers)
Closed 4 years ago.
I recreated the question and describe its essence more precisely.
I have two view controllers presented modally on Swift 4, without storyboard (can't use unwind) and without navigation controller
A presents B which presents C.
The reason why we don't use navigation controller, it's because we what simple animation from bottom to top and instead of breaking the standard animation from right to left, we decided to use present.
I would like to dismiss 2 view controllers and go from C to A.
Please, don't mark this question as duplicate before you read my question carefully. I found a tone of similar post, but neither solved my problem. Some of them Objective-C or some of the suggest to use:
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
Or:
self.presentingViewController?.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: true, completion: nil)
It's works, but it create weird animation. It's just delete C and animate dismiss for B:
Expected result:
Idea: you need to dismiss third controller with animation and after dismissing you need to dismiss second without animation. While third controller is being dismissed, second shouldn't be visible.
First, set Presentation style of second view controller to Over Current Context when you're presenting it (since we will need to hide its view when we will dismiss third controller)
let vc2 = VC2()
vc2.modalPresentationStyle = .overCurrentContext
present(vc2, animated: true)
continue with creating callbacks properties for willDismiss and didDismiss inside third controller. This callback will be called before and after you dismiss third controller
class VC3: UIViewController {
var willDismiss: (() -> Void)?
var didDismiss: (() -> Void)?
#IBAction func dismissButtonPressed(_ sender: UIButton) {
willDismiss?()
dismiss(animated: true) {
self.didDismiss?()
}
}
}
then in second view controller in the place where you present third view controller, set third controller's callback properties: declare what happens when third controller will dismiss: you need to hide view of second and then after dismissing third you need to dismiss second without animation (view can stay hidden since view controller will be deinitialized)
class VC2: UIViewController {
#objc func buttonPressed(_ sender: UIButton) {
var vc3 = VC3()
vc3.willDismiss = {
self.view.isHidden = true
}
vc3.didDismiss = {
self.dismiss(animated: false)
}
present(vc3, animated: true)
}
}
Anyway, second option is using UINavigationController and then just call its method popToViewController(_:animated:).
Create a snapshot from the currently visible view and add it as a subview to the first presented view controller. To find that you can simply "loop through" the presenting view controllers and dismiss from the initial one:
#IBAction func dismissViewControllers(_ sender: UIButton) {
var initialPresentingViewController = self.presentingViewController
while let previousPresentingViewController = initialPresentingViewController?.presentingViewController {
initialPresentingViewController = previousPresentingViewController
}
if let snapshot = view.snapshotView(afterScreenUpdates: true) {
initialPresentingViewController?.presentedViewController?.view.addSubview(snapshot)
}
initialPresentingViewController?.dismiss(animated: true)
}
This is the result with slow animations enabled for the dismissal:
https://www.dropbox.com/s/tjkthftuo9kqhsg/result.mov?dl=0
If you are not using a navigation controller and you want to dismiss all ViewControllers to show the root ViewController (assuming A is the root):
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
Use this in button action method.
The current VC will dismiss when you dismiss the parent VC.
This will dismiss both VCs in single animation.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)

Go back to previous ViewController not working for multiple view controllers in Swift

How to return back to previous view controller programmatically? I found this answer, but there is an example that demonstrate how to go back if we have navigation stack:
self.dismiss(animated: true, completion: nil)
It's ok in case my queue of controllers based on navigation controller.
There are multiple viewControllers all connected with navigationView Controller when I am going to next view controller and tabbing back are working fine but from next to next , I am tabbing back and stuck in a loop means I am stuck between 2nd and 3rd view controller not going to first view controller.
Try this :-
//For moving to previous screen
self.navigationController?.popViewController(animated: true)
//For moving to your root view controller ! This will clear your //navigation stack.
self.navigationController?.popToRootViewController(animated: true)
//To move to a specific view controller eg:- TargetVC
for obj in (self.navigationController?.viewControllers)! {
if obj is TargetVC {
self.navigationController?.popToViewController(obj, animated: true)
break
}
}
Dont use dismiss. From A to B use navigationcontroller.pushviewcontroller and for B to C use presentviewcontroller.
Then on back button of C controller use dismissViewcontroller and on back button of B controller use popViewController.
In swift 3, try this code in button action
#IBAction func btnBackClick(_ sender: UIButton) {
_ = navigationController?.popViewController(animated: true)
}
In a navigation stack (A -> B -> C),
To move 1 controller back (B <- C or A <- B), use
self.navigationController?.popViewController(animated: true)
To move to root view controller (A <- C), use
self.navigationController?.popToRootViewController(animated: true)
Pop PreviousViewcontroller
self.navigationController?.popViewController(animated: true)
Pop To Root Viewcontroller
self.navigationController?.popToRootViewController(animated: true)
Pop To Specific Viewcontroller
let viewControllers: [UIViewController] = self.navigationController!.viewControllers ;
for vc in viewControllers
{
if vc.isKind(of:YourViewController)
{
self.navigationController!.popToViewController(vc, animated: true)
break;
}
}
}
For Swift 3.0 You can use this :-
if you want to back your rootviewcontroller use This
_ = self.navigationController?.popToRootViewController(animated: true)
or if you want to go back another view controller which is added on Stack :-
for obj in (self.navigationController?.viewControllers)! {
if obj is TargetVC {
_ = self.navigationController?.popToViewController(obj, animated: true)
break
}
}

Navigation error swift 3

I was trying to implement the cancel bar button as you can see from the image, which return to the previous viewController using dismiss, but when I click the button, nothing appears, do you know why?
Here's my code to implement that:
#IBAction func cancel(_ sender: UIBarButtonItem) {
let isPresentingInAddTaskMode = presentingViewController is UINavigationController
if isPresentingInAddTaskMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The AddTaskVC is not inside a navigation controller.")
}
}
Thanks in advance.
If I'm reading your question right, this is what your view controller hierarchy looks like:
For adding a new task:
Navigation Controller --(containing)--> "Managing Mode" --(modal)--> Navigation Controller --(containing)--> "Add New Task"
For editing a task:
Navigation Controller --(containing)--> "Managing Mode" --(push)--> "Add New Task"
The problem is that neither of your cases will actually work.
isPresentingInAddTaskMode
In the first case, we see this:
let isPresentingInAddTaskMode = presentingViewController is UINavigationController
if isPresentingInAddTaskMode {
dismiss(animated: true, completion: nil)
}
I'd assume this would handle the case involving the modal presentation.
This will never be true, because presentingViewController is the "Managing Mode" view controller, not the navigation controller containing it or the navigation controller containing the "Add New Task" view controller.
Another problem with this is that you have to dismiss the navigation controller containing "Add New Task", not "Add New Task" itself. This means that instead of
dismiss(animated: true, completion: nil)
you would do
navigationController?.dismiss(animated: true, completion: nil)
owningNavigationController
In the second case, we see this:
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
The first line makes sense: you're unwrapping the navigation controller. However, you then call popViewController(animated: true) on the navigation controller.
The problem with that is that both the modal and the push segues involve a navigation controller, so this case will work for both.
The Solution
You need to form a simpler cancel method using the above:
#IBAction func cancel(_ sender: UIBarButtonItem) {
guard let owningNavigationController = navigationController else {
fatalError("The AddTaskVC is not inside a navigation controller.")
}
if owningNavigationController.presentingViewController?.presentedViewController == owningNavigationController {
// modal
owningNavigationController.dismiss(animated: true, completion: nil)
} else {
// push
owningNavigationController.popViewController(animated: true)
}
}
This first unwraps the navigation controller and errors out if there is none, like what you did originally.
You then check, through a more complex way than before, if the modal segue occurred. This checks the navigation controller's presentingViewController and if it is itself. If so, it's modal and it dismisses itself. If not, it's a push segue and you pop the current view controller.

Go back to previous view controller doesn't work

My first view controller has a button, which triggers the #IBAction goTo2ndVc() which presents a second ViewController:
class FirstVC: UIViewController {
...
#IBAction func goTo2ndVc() {
let secondVc = SecondVC(label: "I am second vc.")
self.presentViewController(secondVc, animated: true, completion: nil)
}
When the button is pressed, the 2nd view controller is shown on screen. No problem.
In 2nd view controller, there is also a button which is used to go back to 1st view controller:
class SecondVC: UIViewController {
...
#IBAction func backToFirst(sender: AnyObject) {
print("go back ...")
self.navigationController?.popViewControllerAnimated(true)
}
}
I looked on internet, people suggest to use navigationController?.popViewControllerAnimated(true) to go back to previous controller. But when I press the go back button I can see the print message "go back ..." but the app doesn't go back to 1st view controller. WHY?
#IBAction func backToFirst(sender: AnyObject) {
print("go back ...")
self.dismissViewControllerAnimated(true, completion: nil)
}
In Swift 3
self.dismiss(animated: true, completion: nil)
you should not use navigation controller, because you didn't use it when you were adding the second view controller. that's why simply call dismissViewControllerAnimated method.
You have to use UINavigationController and its pop methods only when you add your view controllers via pushViewController method.
Familiarize yourself with the concept of navigation controller here: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/NavigationControllers.html
there
the issue is very simple..
self.presentViewController(secondVc, animated: true, completion: nil)
the code will present second view, you are not pushing it.
self.navigationController?.popViewControllerAnimated(true)
the popViewController will pop back to the previous view controller from where it is been pushed.
So, there are two ways you can achieve what you want
1)If you want to present viewController then you have to dismiss the view controller to show previous view controller with
self.dismissViewControllerAnimated(true, completion: nil)
2)If you want to use PopToVewcontroller, then you have to push you second view controller instead of presenting it with
self.navigatioVonroller?.pushViewController(secondVc, animated: true)
If you want to return to the previous view controller, you can simply add:
[self dismissViewControllerAnimated:YES completion:nil];
to the button action method.
If this is added on the nav view controller present on every screen, I see no reason why it shouldn't work as it would always dismiss the most recently presented view.

Resources