Navigation error swift 3 - ios

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.

Related

Segueing to New View Controller while Dismissing previous without navigation controller: swift

Hello I am pretty annoyed with this:
Originally, I had many segues in my storyboard. Each button at bottom of tool bar would segue to various view controllers. As you can imagine, that is a lot of segues for 6 different items on toolbar. After segueing, each action would call self.dismiss and everything would be ok.
Recently, I wanted to clean up the storyboard.
I created the function:
extension UIViewController {
func segue(StoryboardID: String) {
let popOverVC = UIStoryboard(name: "Main", bundle:
nil).instantiateViewController(identifier: StoryboardID)
popOverVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen
popOverVC.modalTransitionStyle = .crossDissolve
self.dismiss(animated: false, completion: nil)
print("dismissed")
self.present(popOverVC, animated: false, completion: nil)
print("presented")
}
}
What I am seeing is that the dismiss dismisses the new view controller from appearing. It essentially goes back to my first view controller presented upon launch. I want to dismiss all view controllers so that I don't keep piling up my views.
Thanks
The problem is that you present your popOverVC from a view controller that is being dismissed, so your popOverVC will never be shown as its parent is being dismissed.
Not sure how your architecture is exactly but you would either need to use the delegate pattern to tell the parent when to segue, for example:
self.dismiss(animated: true) {
self.delegate?.didDismiss(viewController: self)
}
Or to use some trick to get the top most view controller to present your popOverVC after the current view has been dismissed, ex:
self.dismiss(animated: true) {
var topController: UIViewController = UIApplication.shared.windows.filter{$0.isKeyWindow}.first!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
topController.present(popOverVC, animated: true)
}
But I agree with #Paulw11, a UITabBarController seem like a much better option for what you're trying to do.

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.

Failed dismiss 2 view controller

I have 3 ViewController : LoginViewController, CheckinViewController, & ProfileViewController
The flow is :
LoginVC --> CheckinVC --> ProfileVC
What i need is:
I want to dismiss "ProfileVC" & "CheckinVC" when click logout button in "ProfileVC" then go back to the "LoginVC"
LoginVC.swift
let checkinViewController = self.storyboard?.instantiateViewController(withIdentifier: "CheckinViewController") as! CheckinViewController
self.navigationController?.pushViewController(checkinViewController, animated: true)
JustHUD.shared.hide()
self.dismiss(animated: false, completion: nil)
CheckinVC.swift
if let profileView = self.storyboard?.instantiateViewController(withIdentifier: "ProfileViewController") {
profileView.providesPresentationContextTransitionStyle = true
profileView.definesPresentationContext = true
profileView.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext;
// profileView.view.backgroundColor = UIColor.init(white: 0.4, alpha: 0.8)
profileView.view.backgroundColor = UIColor.clear
profileView.view.isOpaque = false
self.present(profileView, animated: true, completion: nil)
Here is i'm trying to do
ProfileVC.swift
#IBAction func clickLogout(_ sender: Any) {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
UserDefaults.standard.synchronize()
self.dismiss(animated: false, completion: {
print("ProfileView : dismiss completed")
let loginViewController = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
self.navigationController?.pushViewController(loginViewController, animated: true)
self.dismiss(animated: false, completion: {
print("SUCCESS")
})
})
}
Well you need to do an Unwind Segue so you can go back in your LoginVC.
Follow these simple four steps to create Unwind segues:
In the view controller you are trying to go back to, LoginVC in your example, write this code:
#IBAction func unwindToVC1(segue:UIStoryboardSegue) { }
( Remember: It’s important to insert this method in the view
controller you are trying to go back TO! )
In the Storyboard, go to the screen you’re trying to unwind from ( ProfileVC in our case ), and control + drag the viewController icon to the Exit icon located on top.
3. Go to the document outline of the selected viewController in the Storyboard, select the unwind segue as shown below.
Now, go to the Attributes Inspector in the Utilities Pane and name the identifier of the unwind segue.
Finally, write this code where you want the unwind segue action to be triggered, ProfileVC in our case.
#IBAction func clickLogout(_ sender: Any) {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
UserDefaults.standard.synchronize()
performSegue(withIdentifier: "unwindSegueToVC1", sender: self)
}
For more information check Create Unwind Segues
While Enea Dume's solution is correct if you want to use storyboards, here is the explanation of the problem, and the solution if you want to do it in code like you have been so far.
The Issue
If we focus on the self.dismiss calls in the logoutFunction in ProfileVC, this is what happens.
The first time you call self.dismiss, ProfileVC will dismiss itself and be removed from the view stack.
In the completion delegate, you are pushing a new LoginVC to a navigation controller. However, the CheckIN VC is presented over the navigation controller so you can't see anything happen.
The second call to self.dismiss does nothing as ProfileVC is not presenting any other view controllers and it is not in the stack any longer.
The Solution
You need to keep a reference to LoginVC that presented the CheckInVC. If you call "reference to LoginVC".dismiss, it will dismiss the view controllers above it in the stack and take you back to the login view controller.
In the class CheckinVC.swift, in the function viewDidAppear, check if the user is still active or not based on the session that you are maintaining and the pop to login view controller accordingly. If user status is logged out, then it will go to login view controller. Else it will work as usual.
Problem is that you are trying to dismiss twice ProfileVC but what you actually need is to dismiss it and then pop CheckinVC.
Also after dismissing a view controller it no longer has a reference to the NavigationController so you need a reference to it in a temporal variable.
Change ProfileVC like this:
#IBAction func clickLogout(_ sender: Any) {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
UserDefaults.standard.synchronize()
let navigationController = self.navigationController
let loginViewController = storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
self.dismiss(animated: false, completion: {
print("ProfileView : dismiss completed")
navigationController?.pushViewController(loginViewController, animated: true)
navigationController?.popViewController(animated: false)
})
}

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)

Programmatically go back to previous ViewController in Swift

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

Resources