How to properly set up UITabBarController programmatically with separate storyboards - ios

I have three storyboards in my project. There is a main and two separate workflow storyboards. Each storyboard is embedded in its own navigation controller.
Since I have broken up the storyboards into workflows I have to programmatically add each workflow to the tab bar. This is working correctly.
My issue occurs when I try and push a view (within a workflow) onto the workflows navigation controller. It seems that the navigation controller for a workflow is never being used. I verified this by changing the navigation bar color for each workflow.
I have tried two options, each set up on a workflow.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let mainStoryboard = UIStoryboard(name: "Workflow 1", bundle: nil)
let actionItemStoryboard = UIStoryboard(name: "Workflow 2", bundle: nil)
let workflow1Controller = mainStoryboard.instantiateViewController(withIdentifier: "navigationController1")
let workflow1TabItem = UITabBarItem(title: "Item 1", image: nil, selectedImage: nil)
workflow1Controller.tabBarItem = workflow1TabItem
let workflow2Controller = actionItemStoryboard.instantiateViewController(withIdentifier: "workflow2")
let workflow2TabItem = UITabBarItem(title: "Item 2", image: nil, selectedImage: nil)
workflow2Controller.tabBarItem = workflow2TabItem
self.viewControllers = [workflow1Controller, workflow2Controller]
}
Option 1
Setting the tab view controller to point to the navigation controller (pretty sure this is incorrect but was a test I did). This displays the views how I want but doesn't show the navigation item (back button).
Option 2
Setting the tab view controller to point to the list view (see screen shots below). This displays the back button but upon clicking on the cell the last view is displayed "over" the tabs.
Main Storyboard
Workflow 1 Storyboard
Workflow 2 Storyboard

In order to solve this I ended up doing the following:
Remove the navigation controllers from the workflows
Create a "MockTabController" (just a UIViewController with a UITabBar placed on the bottom of the view)
Now that I have a UIViewController with a UITabBar I can have each workflow view extend this view instead of a UIViewController and thus a tab bar is consistent throughout my app (where I want it).
For instances where the workflow has a UITableViewController I simply embed the UITableViewController inside a ViewController as a child view. The ViewController then extends my MockTabController and the result is a TableViewController with a tab bar that doesn't need any modifications to work.
In order to simplify the navigating throughout the app, I simply reset the navigation stack back to the beginning of the tab controller. Clicking a tab bar item unwinds all the workflow and then pushes the start of a new workflow.

Related

How do I switch between screens via the menu? A big error appears. Swift

I have a navigation controller and several view controllers. When I click on a table cell in the Stocks View Controller, I open the Stock Chart View Controller. In it I have 3 buttons "Chart", "Summary", "News". I want to click on "Summary" to move to the Stock Summary View Controller, but an error occurs for several dozen lines. How can I implement transitions in such a menu?
And how do I click on" back" in Stock Chart View Controller and Stock Summary View Controller to return to Stocks View Controller?
Code for switching from the Stock Chart View Controller to the Stock Summary View Controller:
#IBAction func onSummaryButtonTapped(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let viewController = storyboard.instantiateViewController(identifier: "StockSummaryViewController") as! StockSummaryViewController
navigationController?.pushViewController(viewController, animated: true)
}
Storyboard:
enter image description here
Your App:
NavigationVC > StocksVC (tapping on a cell) ➡ StockChartVC > StockSummaryVC > etc.
➡ is important here, how do you navigate from StocksVC to StockChartVC? Do you push StockChartVC or present modally? Since you do segues programmatically, it can't bee seen in the image.
I can provide the following info to help you:
The push-style navigation automatically contains/adds controls for backward navigation because it adds all ViewControllers to its navigation stack; all ViewControllers will be embedded in the same NavigationController
In case of presenting scenes modally, these ViewControllers have to have their own NavigationController. thus, your StockChartVC has to be embedded in its own NavigationController
I believe you do the push-style segue from StocksVC to StockChartVC and also pushing from StockChartVC to StockSummaryVC; in that case, there should be no problem at all. Moreover (as I wrote), all they VCs will be added controls for backward navigation. Everything will be working just fine!
So pay attention how you are segueing and according to that, implement the right navigation.

Navigation bar won't show after pushing a view controller

I'm trying to move from a View Controller to another.
I worte this function to use when the user tap on a button to move to the new view controller:
#objc private func infoButtonTap(){
let navVC = UINavigationController()
navVC.addChild(AboutViewController())
self.navigationController?.pushViewController(AboutViewController(), animated: true)
}
The problem is that the new view controller is presented on the screen but I don't have a navigation bar and a back button to move back.
I do not use Storyboard as I want to learn coding the UI.
I tried few things I found here on Stackoverflow but none worked for me.
How can I set the new view controller to have a navigation bar with back button?
UINavigationController has a variable isNavigationBarHidden
#objc private func infoButtonTap(){
let navVC = UINavigationController()
navVC.addChild(AboutViewController())
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.pushViewController(AboutViewController(), animated: true)
}
You need to push the view Controller. Try this
let aboutVC = AboutVC()
self.navigationController?.pushViewController(aboutVC, animated: true)
You don't need to write any code.
Select the Root Navigation Controller the will control the app. In the Inspector Bar, select Simulated Metrics ( The Third Selection from the Right in the Inspector) and Check the Box " Is Initial View Controller". Then connect the next View controller which will be in essence the Landing page for the app. Once you connect other View Controllers to that View controller via a button for instance ( Select the button, then press Control Key + Drag to View Controller, select Show) , you will see the navigation Bar with "Back" displayed. Once that's done, you can add other view controllers and connect them from the landing page view controller and the Navigation Bars will be displayed.
For navigation to be visible and of use in an app , first you need to set up a Navigation controller with a Root view controller i.e your first controller and from there you can use push method on your navigation controller object to push a controller on to the stack.
For eg
let navVC = UINavigationController.init(rootViewController: YourFirstViewControllerObject())
navVC.pushViewController(NewViewControllerObj(), animated: true)

can we programatically set to which tabar item should be displayed when a tab bar is shown?

I am working on a project which contain a tab bar. The tab bar contain 2 items named leave and Od. Both are normal viewcontroller class. They have a table view inside it(I dont mean table view controller). While clicking on a item in a table view a pop Up screen appears which have a viewcontroller swift file named as popUpviewController. This show details of item selected in table view. The problem is when i dismiss the popUpdialog i always get the selected tabBar item as the default one.Here its leave authorise.
what I did is i gave an Storyboard Id to tab Bar controller and called it from the popUp when its dismissed, like this.
let viewController:UIViewController = UIStoryboard(name:self.whichSB!, bundle: nil).instantiateViewControllerWithIdentifier("AuthoriseTabBar?") as UIViewController
self.tabBarController?.selectedIndex=2 /* DOESN'T WORK OBVIOUSLY*/
self.presentViewController(viewController, animated: false, completion: nil)
** The tab bar controller doesn't have any associated class with it.I would like to show item 1 when item1 popUp is dismissed(This works as its now the default item shown in tab bar), and item 2 when item 2 popUp is dismissed.**
Can anyone suggest a away of doing the above . and I havent used any navigation controller here, is it necessory to get tabbar.selectedindex of tab bar
You can access the tab bar, from the popup VC, like this. Just run this with the normal dismiss line as shown.
if let presentingVC = self.presentingViewController {
if let tabController = presentingVC.tabBarController {
tabController.selectedIndex = 0 // Whatever index you want to select.
}
}
self.dismiss(animated: true, completion: nil)

How to make dynamic uitabbar controllers?(swift)

I make an app that has 3 tabs. I want to make my tab bar controller dynamically change the view controller of the first tab bar according to the logged in user type. The rest two tab bars are static. For example, if the user type is 1, I want to show ViewController1 for the first tab, and if the user type is 2, I want to show ViewController2 instead. Is this impossible to achieve when the tab bar is designed in the storyboard? I use storyboard in the app.
You can change these by using the function on a tabview controller setviewcontrollers (see documentation here: https://developer.apple.com/documentation/uikit/uitabbarcontroller/1621177-setviewcontrollers).
For instance I have a storyboard with 4 Viewcontrollers in a tabviewcontroller, and then in the first viewController that loads, I added the following in viewDidLoad()"
let storyboard = UIStoryboard(name: "myStoryboard", bundle: nil)
let newTab = storyboard.instantiateViewController(withIdentifier: "testControllerID") as! TestViewController
let myControllers = [newTab, tabButton2, tabButton3, self, tabButton4]
tabBarController?.setViewControllers(myControllers, animated: true)
So I was able to rearrange my previously defined tabs as well as add "newTab", which is a VC designed on the storyboard, given an identifier, but not actually added to the tabViewController.
Hope that helps

How to push new view in detail view controller from master view controller?

I have been scratching my head trying to figure out how to do this. If anyone has some insight it would be greatly appreciated. I've attempted to do this using segues and push/presentViewController methods. With pushViewController nothing happens.
Scenario: Split view controller has two navigation controllers connected (one as master, one as detail). The master's navigation controller has a form with various cells that should control what is being displayed in the right hand side detail view when in landscape mode on the iPad. The navigation controller connected to the detail view has storyboard references connected to it (3 of them).
What I want to do: From the master view controller (which is the app menu), I would like to control what is being displayed in the detail view while maintaining navigation bar.
Attempt 1:
let detailVC = self.splitViewController!.viewControllers[1]
let newVC = UIStoryboard(name: "D", bundle: nil).instantiateViewControllerWithIdentifier("P")
detailVC.self.navigationController?.pushViewController(newVC, animated: true)
Attempt 2:
let detailVC = self.splitViewController!.viewControllers[1]
let newVC = UIStoryboard(name: "D", bundle: nil).instantiateViewControllerWithIdentifier("P")
detailVC.performSegueWithIdentifier("navP", sender: self)
One other related question I had...if a user does many hops between several of the menu options, how can one "reset" the back button's history in the navigation bar to prevent a case where clicking back will cycle you through the same several views?
You shouldn't be pushing the view controller or performing a segue but calling showViewController.
Do you definitely need to maintain the navigation bar or can you show different UINavigationBars (via showing your view controller embedded in an UINavigationViewController potentially)?
Alternatively just show a single view controller and use the logic you add in your view controller to change the content under the control of your master view controller.
After some experimenting with the best approach I solved this. :) Solution below for all devices (iPhone/iPad).
Define extension for UISplitViewController:
Modified version based off https://stackoverflow.com/users/4418308/santiago-bendavid
extension UISplitViewController {
func toggleMasterView() {
if UIScreen.mainScreen().bounds.height > UIScreen.mainScreen().bounds.width {
var nextDisplayMode: UISplitViewControllerDisplayMode
switch(self.preferredDisplayMode){
case .PrimaryHidden:
nextDisplayMode = .AllVisible
default:
nextDisplayMode = .PrimaryHidden
}
UIView.animateWithDuration(0.2) { () -> Void in
self.preferredDisplayMode = nextDisplayMode
}
} else {
// do nothing
}
}
}
Code in master navigation controller's root view controller:
let newVC = UIStoryboard(name: "some_storyboard_id", bundle: nil).instantiateInitialViewController()
if self.splitViewController!.viewControllers.count == 2 {
let detailVC = self.splitViewController!.viewControllers[self.splitViewController!.viewControllers.endIndex - 1]
if detailVC.childViewControllers[detailVC.childViewControllers.count - 1].restorationIdentifier! != "some_id_here" {
detailVC.childViewControllers[0].navigationController?.pushViewController(newVC!, animated: true)
}
self.splitViewController!.toggleMasterView()
self.navigationController?.splitViewController!.preferredDisplayMode = .Automatic
} else {
self.navigationController?.pushViewController(newVC!, animated: true)
}
This code works on regular iPhone (non-Plus) where the master view controller is the sole controller used for navigation. On iPads and iPhone 6/6s+ models (which behave same as an iPad) I check whether the current view is already present using the restoration ID property, and then present the new view if it's not the same as the one already rendered on screen and dismiss the master view controller if we're in portrait mode. If in landscape, we keep it on screen (default behavior).

Resources