How to dismiss controller and then switch tabs - swift

I have two main VC that are in a tab bar controller. On the first VC when you tap a button you are taken to another VC (not the 2nd tab bar VC) and when you tap the finish button I want to go to the 2nd tab bar vc.
Normally I would just perform segue ... but when I do that it removes the tab bar at the bottom. So, I am now just dismissing and going back to the original VC, but I would really like to go to the 2nd tab bar vc so I tried this
dismiss(animated: true) {
self.tabBarController?.selectedIndex = 2
}
I saw the self.tabBar... on another SO post, but I can't seem to find where I set the Index...ive tried 1 in case it just automatically starts from 0, and Ive tried 2 in case it starts from 1
Am I close or is there a better way of achieving what I want?
Summary
To summarize clearly. I have 3 View Controllers. 1 and 3 are in a Tab Bar Controller. VC 2 is NOT in the TBC. To get to VC 2 there is a button on VC 1. When the user is finished on VC 2 he/she taps a button. Instead of just dismissing back to VC 1 I want to go to VC 3, but when I perform segue it goes to VC 3 but removes the Tab Bar

Make sure you have a UINavigationController as the first ViewController. Then you have access to the tabBarController and can then switch the tabs:
// if vc was pushed
#IBAction func onFinish(_ sender: Any) {
// store tabBarController for later use
let tabBarController = self.tabBarController
// pop to root vc (green one)
_ = self.navigationController?.popToRootViewController(animated: false)
// switch to 2nd tab (red vc)
tabBarController?.selectedIndex = 1
}
// if vc was presented modally
#IBAction func onFinish(_ sender: Any) {
self.dismiss(animated: false, completion: nil)
if let tabBarController = self.presentingViewController as? UITabBarController {
tabBarController.selectedIndex = 1
}
}
// if vc was presented modally + switching tab in completion block
#IBAction func onFinish(_ sender: Any) {
if let tabBarController = self.presentingViewController as? UITabBarController {
self.dismiss(animated: true) {
tabBarController.selectedIndex = 1
}
}
}

Indexes will most likely always start from 0 in any language you use.
As for the tab bar you are calling self on the view controller presented not on the view controller that it is dismissing to. You should be able to do
self.window?.rootViewController.tabBarController?.selectedIndex = 1
in order to accomplish this. This might not work based on the setup of your VCs so you might have to play with it.

You can move to specific index before dismiss view controller.
func moveToVC() {
if let mainNavVC = self.presentingViewController as? UINavigationController {
if mainNavVC.viewControllers.count > 0 {
if let tabbarVC = mainNavVC.viewControllers.first as? TabBarViewController {
tabbarVC.selectedIndex = 0
}
}
}
}

Related

Mixed programmatic and storyboard. Button click will not open next VC in a navigation controller

I have a tab bar controller that is built programatically. And it looks something like this:
class NewTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
createTabbar()
}
func createTabbar() {
let deliveryViewController = storyBoard.instantiateViewController(identifier: "DeliveryViewController") as? DeliveryViewController
deliveryViewController?.tabBarItem.image = #imageLiteral(resourceName: "icon_deliver")
deliveryViewController?.title = "Delivery"
planDictionary["planType"] = Constants.Mealplan.deliveryPlan
deliveryViewController?.planDictionary = planDictionary
// Excluded other tabs and view controller creations since they are the same
}
Now this DeliveryViewController is created in storyboard and embedded in a nav controller
It has a button click action:
#IBAction func saveNameButton(_ sender: UIButton) {
let addressViewController = storyBoard.instantiateViewController(identifier: "AddressViewController") as? AddressViewController
addressViewController?.planDictionary = planDictionary
navigationController?.pushViewController(addressViewController!, animated: true)
}
The button click action was working when the tabbar was in the storyboard. But after refactoring programatically, it is does not place the next VC on the navigation stack.
Help would be appreciated.
Almost certainly...
This line:
let deliveryViewController = storyBoard.instantiateViewController(identifier: "DeliveryViewController") as? DeliveryViewController
Is instantiating an instance of DeliveryViewController and setting that as the View Controller for a Tab. But what you want to do is load a UINavigationController and make DeliveryViewController the root view controller of that NavController, and set that NavController as a Tab item.

How to dismiss a navigation controller?

I'm presenting a navigation controller (detailview) with showDetailViewController(). I want to dismiss it when a button is pressed. How can I dismiss this viewcontroller?
Code I've attempted:
//detailviewcontroller
#objc
func cancel(_ sender: Any) {
print("cancelPressed")
//self.navigationController?.popViewController(animated: true)
//self.navigationController?.dismiss(animated: true, completion: nil)
//self.dismiss(animated: true, completion: nil)
//splitViewController?.dismiss(animated: true, completion: nil)
//splitViewController?.navigationController?.popToRootViewController(animated: true)
//splitViewController?.navigationController?.popViewController(animated: true)
//splitViewController?.navigationController?.dismiss(animated: true, completion: nil)
//navigationController?.popViewController(animated: true)
//navigationController?.dismiss(animated: true, completion: nil)
//self.navigationController?.popToRootViewController(animated: true)
//self.navigationController?.viewControllers.remove(at: 1)
//self.navigationController?.viewControllers.remove(at: 0) - this one removes to blank view
//self.presentingViewController?.dismiss(animated: true, completion: nil)
}
I've tried multiple stackoverflow solutions:
Dismissing a navigation view controller
How to dismiss a view controller in a navigation controller, without dismissing the whole stack
ios swift - dismiss root view controller of navigation controller
Can't dismiss View Controller that's embedded in a Navigation Controller
How to dismiss a navigation controller presented from another navigation controller in iOS 10 and below?
Can't dismiss navigation controller in Swift
Can't dismiss navigation controller in Swift
Dismissing View Controller Doesn't Work While Using Navigation Controller
Dismiss current navigation controller when clicked tab bar
How to dismiss a certain view controller
How I'm presenting the detailviewcontroller:
//masterviewcontroller
// delegation for passing data between controllers
weak var passDelegate: PlaceSelectionDelegate?
func insertNewObject(_ sender: Any) {
if let detailViewController = passDelegate as? DetailViewController {
if let detailNavigationController = detailViewController.navigationController {
detailViewController.delegate = self
splitViewController?.showDetailViewController(detailNavigationController, sender: nil)
}
}
}
Expected results:
dismiss detail viewController on button press.
Actual results:
No dismiss.
Ok, I've been looking for a way of dealing with this showDetailViewController for a while now and although I'm not sure this answers your question, this is how I did it:
I have two controllers, let's call it FirstController and SecondController. They both inherit from UIViewController. When I click a certain navbar button in the FirstController, I use showDetailViewController to show SecondController as a sheet moving upwards, like the "calendars" button in the Calendar app.
In FirstController I implemented 2 functions: one to show the SecondController and one to dismiss it. I call the dismiss action from within the SecondController. It looks like this:
// FirstController
#objc func showSecondView() {
let vc = SecondController()
vc.delegate = self
let nav = UINavigationController(rootViewController:vc)
self.showDetailViewController(nav, sender: self)
}
func closeSecondView() {
self.dismiss(animated: true)
}
As you can see I wrapped my SecondController in a UINavigationController, so it shows the navbar with title and all. This is how the code looks like:
// SecondController
weak var delegate: FirstController!
override func viewDidLoad() {
super.viewDidLoad()
title = "Second"
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeMe))
}
#objc func closeMe() {
delegate.closeSecondView()
}
Notice how I made a property for the FirstController and I use it to dismiss the SecondController. When I presented SecondController I assigned self as delegate, which means I can now call the closeSecondView method from within it.
I am not sure whether it works out of the box with split-view. The documentation seems to suggest that whenever you use it, if the screen is big enough, it behaves differently because UISplitViewController overrides it. It also says there that all view controllers have this method.
Take a look: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621432-showdetailviewcontroller
Give it a go, I hope this helps.
This should work if you simply want to dismiss the navigation controller
navigationController?.dismiss(animated: true, completion: nil)

perform segue after view controller is dimissed

I am using UINavigationController. I want to show an intermediate screen eg. White and then from there I want to dimiss and segue to green.
The reason I don't create a segue from white to green is because in the case the user goes back they should go back to blue because blue is my main screen.
Here's the code:
class BlueViewController: UIViewController {
#IBAction func tapBlue(_ sender: Any) {
self.performSegue(withIdentifier: "whiteSegue", sender: self)
}
}
class WhiteViewController: UIViewController {
#IBAction func tapGreen(_ sender: Any) {
navigationController?.popViewController(animated: true)
weak var pvc = self.presentingViewController
dismiss(animated: true){
pvc?.performSegue(withIdentifier: "greenSegue", sender: self)
}
}
}
Here's the codebase
https://github.com/omenking/DismissAndSegue
No error occurs but when white is dismissed it doesn't go to green.
I know this has been asked before on StackOverflow but the other examples did not work or were out of date with latest iOS.
The main issue is that since you are using a navigation view controller and pushing view controllers on and off the stack, the self.presentingViewController variable will be nil. That is used for modal presentations, not navigation view controllers.
Try this:
class WhiteViewController: UIViewController {
#IBAction func tapGreen(_ sender: Any) {
// Get navigation stack, remove last item (White VC)
var viewControllers = navigationController?.viewControllers
viewControllers.removeLast()
// Instantiate new Green VC from storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil) //Change bundle name
let greenViewController = storyboard.instantiateViewController(withIdentifier: "GreenViewController") //Change storyboard ID
viewControllers.append(greenViewController)
// Perform the transition to Green VC with animation
navigationController?.setViewControllers(viewControllers, animated: true)
}
}
A slightly different solution is to link your view controllers blue->white->green, and then in the green view controller, just remove the white view controller from the navigation stack.
Your green view controller then becomes as simple as this.
class GreenViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let count = self.navigationController?.viewControllers.count {
self.navigationController?.viewControllers.remove(at: count - 2)
}
}
}
No special handling needed in the other view controllers.
I do this, in white controller
performSegue(withIdentifier: "showSchedule", sender: date)
if let count = self.navigationController?.viewControllers.count {
self.navigationController?.viewControllers.remove(at: count - 2)
}

How can I add a segue identifier to a programmatically modal transition?

I have a controller which is in the Main storyboard. When I click on a button, I call the displayBorneDetailsAction() action, it presents a modal view from another storyboard.
I would add a Segue identifier when I present my modal to pass data from my main view controller to my modal view controller (with prepareForSegue), but I don't know how to do it.
I tried to use performSegue(withIdentifier:), but it doesn't present the modal in the same way.
#IBAction func displayBorneDetailsAction(_ sender: Any) {
// open the modal of a borne
let storyboard : UIStoryboard = UIStoryboard(name: "Borne", bundle: nil)
let vc: BorneVC = storyboard.instantiateViewController(withIdentifier: "BorneVC") as! BorneVC
let navigationController = UINavigationController(rootViewController: vc)
navigationController.modalPresentationStyle = UIModalPresentationStyle.overFullScreen
navigationController.edgesForExtendedLayout = []
self.present(navigationController, animated: true, completion: nil)
}
You cannot add an identifier to a programmatical segue, since you are able to access the instance of the controller that is being presented. Simply do anything you want with the controller in the function where you present it.

Swift - How to remove a viewcontroller from NavigationController?

I've got 4 ViewControllers attached to a NavigationController. The order of them is 1->2->3->4. When the user presses the back button on 4, I'd like them to be redirected to 2 instead of 3. At the same time, I'd also like the user to be directed back to 2 when the back button is pressed on 3. Is this possible? Thanks in advance.
Of course you can do this. Simply create the left bar button on 4th ViewController. and on that button action pop to 2nd viewcontroller
if let viewcontroller = self.navigationController?.viewControllers[1] where viewcontroller.isKindOfClass(YourController) {
self.navigationController?.popToViewController(viewcontroller, animated: false) }
if let vc = self.viewControllerWithClass(YourVC.self) {
self.popToViewController(vc, animated: true)
}
extension UINavigationController {
func viewControllerWithClass(_ aClass: AnyClass) -> UIViewController? {
for vc in self.viewControllers {
if vc.isMember(of: aClass) {
return vc
}
}
return nil
}
}
You can check for the controller in navigation stack.
let controllers = navigationController!.viewControllers.reverse()
for controller in controllers
{
if controller.isKindOfClass(YourController)
{
self.navigationController?.popToViewController(controller, animated: true)
return
}
}