Because I want to redesign the tab bar UI, I wrote a custom tab bar controller according to https://github.com/codepath/ios_guides/wiki/Creating-a-Custom-Tab-Bar
In TabBarViewController's viewDidLoad(), define several subviews corresponding to each tab bar
homeViewController = storyboard.instantiateViewControllerWithIdentifier("HomeViewController")
...
viewControllers = [homeViewController, searchViewController, accountViewController, trendingViewController]
and the main method when tapping tab
#IBAction func didPressTab(_ sender: UIButton) {
let previousIndex = selectedIndex
selectedIndex = sender.tag
tabButtons[previousIndex!].isSelected = false
let previousVC = viewControllers[previousIndex!]
// remove previous VC
previousVC.willMove(toParentViewController: nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
// set current VC
sender.isSelected = true
let vc = viewControllers[selectedIndex]
addChildViewController(vc)
// Adjust the size to match content view
vc.view.frame = contentView.bounds
contentView.addSubview(vc.view)
vc.didMove(toParentViewController: self)
}
I could set a default tab bar index selectedIndex when the tab bar view is loaded. However, how can I switch to next tab bar in homeViewController (without tapping tab bar buttons)?
This doesn't work in homeViewController
TabBarViewController().tabButtons[2].isSelected = true TabBarViewController().didPressTab(TabBarViewController().tabButtons[2])
I'm not sure how to get the running tab controller, set the selectedIndex, and update subview in the subview controllers.
All you need to do is to call tabBar.setSelectedViewController: and pass the view controller.
If you only know the tab index, you call tabBar.viewControllers[index] and get the view controller.
I finally use Delegate to solve.
In SubViewController add protocol
protocol SubViewControllerDelegate {
func transferToView(index: Int)
}
and declare this in the class
var delegate: SubViewControllerDelegate?
In TabBarViewController set to conform SubViewControllerDelegate
Implement the method
func transferToView(index: Int) {
tabButtons[index].isSelected = true
didPressTab(tabButtons[index])
}
Set delegate
subViewController = storyboard.instantiateViewControllerWithIdentifier("HomeViewController")
let subVC = subViewController as! SubViewController
subVC.delegate = self
Related
I need to change tabbar selected item when view updates. So, i have tabbar with 4 items, i want that when clicking on the button that is in the last index, view updates and selected item also stays the last(3). But when i am clicking on the button, the selected item changes and it shows firstviewcontroller. I am using this code in button click
let tabBarVC = UIStoryboard(name: "TabBarViewController", bundle: nil).instantiateViewController(withIdentifier: "CustomTabBarViewController")
let nav = UINavigationController(rootViewController: tabBarVC)
nav.setAsRoot()
When i set selected item to 3 in CustomTabBarViewController's viewDidload it is okay, but i need that when clicking on button that works not always when tabbar is showing.
If you need to refresh all the vcs
let tabBarVC = UIStoryboard(name: "TabBarViewController", bundle: nil).instantiateViewController(withIdentifier: "CustomTabBarViewController") as! CustomTabBarViewController
tabBarVC.selectedIndex = 3
let nav = UINavigationController(rootViewController: tabBarVC)
nav.setAsRoot()
But if you need to refresh only the last vc then
let tabBarVC = self.tabBarController as! CustomTabBarViewController
let vc = // load only 3rd vc from storyboard with it's identifier
tabBarVC.viewControllers![3] = vc
The basic idea is that the tabBarController:shouldSelectViewController: selector in your UITabBarController delegate is called whenever the user clicks on tab item.
Thus, by appropriately defining that method, you get a chance to do your own processing before the current view controller is replaced by the one the user selected by clicking in the tab bar.
So, simply return NO from this selector in case you wish to prevent the current view controller to be replaced, i.e. when a transaction is ongoing.
import UIKit
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self // Delegate self to handle delegate methods.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
let selectedIndex = tabBarController.viewControllers?.firstIndex(of: viewController)!
if selectedIndex == 3 { // <- The index for which you don't want to set view.
// Do anything.
return false
}
return true
}
}
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.
I have a view controller like below.
This view is attached with a tabBarController. The tabBarController has 5 viewControllers and I have to present the 5th viewController of tabBar from another page. So I used the below codes for present that viewController
#IBAction func onClickUserProfile(_ sender: Any) {
let navVc = self.storyboard?.instantiateViewController(withIdentifier: "ProfileVC")as! ProfileVC
navVc.userId = Int(self.userId)
navVc.navigationItem.hidesBackButton = true
navVc.tabBarController?.tabBar.isHidden = false
self.navigationController?.pushViewController(nxtVc, animated: true)
}
But after execute the code it resulting the view controller as the below image.
The view undergoes the tabBar. Anyone help me to push to a tabBar view.
You need to set the selected UIViewController from UITabBarController something like this should work .
self.tabBarController?.selectedViewController = self.tabBarController?.viewControllers![1]
where tabBarController?.viewControllers returns the array of current ViewControllers embedded in the UITabBarController .
Your code should be something like this.
#IBAction func onClickUserProfile(_ sender: Any) {
let vc = self.tabBarController?.viewControllers![1] as! ProfileVC // use your index
vc.userId = Int(self.userId)
self.tabBarController?.selectedViewController = vc
}
Note: Don't create an instance of the UIViewController as .instantiateViewController(withIdentifier:) use the already existed
ones in the array tabBarController?.viewControllers, creating new
instance will be treated as new one and gives the problem you have up
there .
I created a table view and from there let say a user pressed a cell it will go to ListTavleView but the only problem right now is that whenever a user is in ListTableView there is not back button even thought i already embed a navigation controller
and i want the fist view navigation bar is small title second view navigation bar is large title
enter image description here
Below is my code
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showList" {
if let indexPath = tableView.indexPathForSelectedRow {
let items = dataManager.items[indexPath.row]
let controller = (segue.destination as! UINavigationController).topViewController as! ListTableViewController
controller.item = items
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Below is my storybord setup
Navigation bar with no back button
From the image it seems that view controller is added as a child view controller in current view controller.
There is not need to embedded navigation controller when a cell is pressed becoz there is already a navigation controller at start point so no need to create a new one.(If you present a view controller then you may need to embed navigation controller.)
So the solution is...
Delete the navigation controller.
Connect directly to the destination view controller without navigation controller as there is already.
it is better if you use pushViewController, just get a reference of the other view controller, it will always a back button since you are pushing threw navigation Controller here is a simple example:
let story = UIStoryboard(name: "Main", bundle: nil)
let vc = story.instantiateViewController(withIdentifier: "ExampleViewController") as! ExampleViewController
self.navigationController?.pushViewController(vc, animated: true)
as for the back button, the issue is with your hierarchy.
are you changing the left item of navigation bar in another view controller that might affect navigation bar in your destination view controller.
You are pushing new NavigationController(say Nav.B) to the existing one(Nav.A).
Each navigation controller keeps different navigation stack. The back button is visible when you add viewcontroller to Navigation controller. Read more about UINavigationController.
For your current scenario, you could delete the second navigation controller(i think it not essential) & connect direct segue to ListTableViewController
So this
let controller = (segue.destination as! UINavigationController).topViewController as! ListTableViewController
becomes
let controller = segue.destination as! ListTableViewController
When you need large titles(available 11+), you can add this line in viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
And if it needed only for this Viewcontroller, add in viewWillDisappear() or viewDidDisappear()
navigationController?.navigationBar.prefersLargeTitles = false
If you wanted to have navigation bar back button on next view, then just push the target view on navigation, it will show default navigation back button. No, need to any extra work.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showList" {
if let indexPath = tableView.indexPathForSelectedRow {
let items = dataManager.items[indexPath.row]
guard let controller = segue.destination as? ListTableViewController else {
return
}
controller.item = items
self.navigationController?.pushViewController(controller, animated: true)
}
}}
And if you are pushing the viewcontroller with segue, then no need to add below line self.navigationController?.pushViewController(controller, animated: true)
I created a custom search bar and embedding it in the navigation bar, it appears but after I push another view controller, the search bar does not get replaced with the title of the pushed view controller. The search bar stays persistent throughout all views, instead of getting replaced with a title. Perfect example is Instagram search tab, you search for a person and click on the cell, their profile is pushed and the search bar is replaced with the custom title, back button, etc.
First VC
self.customSearchBar.tag = 4
self.navigationController?.view.addSubview(customSearchBar)
Second VC
if let nav: UINavigationController = self.navigationController {
if let searchBar = nav.view.viewWithTag(4) {
searchBar.removeFromSuperview()
}
}
You shouldn't place the searchbar inside the navigationcontroller view as this view is the same instance on all pushed viewcontrollers.
Add the searchbar to the the depending view controllers ui.
To add a searchbar on navigationBar, this is the way.
self.navigationController?.navigationBar.addSubview(customSearchBar)
To remove it when you push it to other viewController. Write the following code in the secondVC that is pushed inside it's viewDidLoad() function. Also, set the tag of customSearchBar to any number (TAG)
if let nav: UINavigationController = self.navigationController {
let bar: UINavigationBar = nav.navigationBar
if let searchBar = bar.viewWithTag(TAG) {
searchBar.removeFromSuperview()
}
}
In the question, the customSearchBar is added to self.navigationController.view. To remove it, you can do the following:
if let nav: UINavigationController = self.navigationController {
if let searchBar = nav.view.viewWithTag(TAG) {
searchBar.removeFromSuperview()
}
}
Edit:
Adding and removing a UIViewController's view as a subview of other UIViewController
// for adding
let viewController: ViewController = ViewController()
self.addChildViewController(viewController)
self.view.addSubview(viewController.view)
viewController.view.bounds = self.view.bounds // better to use autolayout here
viewController.didMove(toParentViewController: self)
// for removing
if let vc = self.childViewControllers.last {
vc.willMove(toParentViewController: nil)
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
}