How to update bottom TabBar without doing the navigation in Swift? - ios

I have an app with a HomeView and a bottom TabBar which load other views on tap. On this TabBar bar I also have a More button which on click opens a listView, each cell of this view also opens other views. (When I'm talking about views, I mean new tunnels/navigation controllers are displayed for example: MyPicturesViewController, SettingsViewController etc...).
There is my problem, I have to implement a feature that from my HomeView on a button click it navigates to a specific view that is not on the TabBar but in the More list and also update the TabBar to highlight the More button.
I tried at first to navigate manually with this code:
let viewController = UIStoryboard.instantiate(NewViewController.self)
self.viewController?.navigationController?.pushViewController(viewController, animated: true)
it works pretty well
and then update the tabbar manually
self.viewController?.tabBarController?.selectedIndex = 4
But updating the TabBar redirect automatically to the selected index which is the More list view and don't even take in count the manual navigation I did in the previous code.
So my question is, is it possible to update TabBar bar without loading its own navigationcontroller? If not how can I achieve what I want to do, I tried many things such as create a navigationcontroller and its viewcontrollers and replace the tabbar viewcontrollers stacks but I didn't succeed.
Thank you in advance for those who will take time to help me!

Instead doing it before
self.viewController?.tabBarController?.selectedIndex = 4
Push your controller in tabBar delegate method
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController == desiredNavigationController {
show(desiredNavigationController, animated: true) // push
return false
}
return true
}

Related

TabBar is hidden after going back to the initial ViewController of a TabBarController Using Segue

I have a tab bar controller with three table view controllers and the second VC is embedded in a navigation Controller. in the second VC, I made the tabBar hidden using this line self.tabBarController?.tabBar.isHidden = true and I created a bar button to go back to the first view controller which is the "home" VC using segue with modal presentation.
Screenshot of my StoryBoard
My problem is after hitting the back button and going back to home VC from the second VC, the tabBar is still hidden even though I put self.tabBarController?.tabBar.isHidden = false in the home VC's viewWillAppear method and the second VC's viewWillDisappear method.
Here is the result that I expected vs what I got
expected home VC
result home VC
How can I make the Tab Bar show?
When you are using the modal presentation segue, you are creating a completely new instance of HomeViewController. The new HomeViewController is not linked to the TabBarController in your hierarchy.
Here's you initial view hierarchy:
TabBarController
-> HomeVC
-> CreateVC (Navigation Controller)
-> CreateQuizVC
-> SavedVC
Now after tapping the back button you'll get the following:
TabBarController
-> HomeVC
-> CreateVC (Navigation Controller)
-> CreateQuizVC
-> HomeVC(2)
-> SavedVC
What you could do is, instead of using the segue to go back, add an IBAction in your code to set the selectedIndex of the TabBar programatically, and link the Back UIBarButtonItem to this IBAction.
#IBAction func backButtonAction(_ backButton: UIBarButtonItem) {
// Keep in mind that the CreateQuizVC is embeded in a NavigationController.
// The NavigationController is the child of the TabBarController
navigationController?.tabBarController?.selectedIndex = 0
navigationController?.tabBarController?.tabBar.isHidden = false
}
However, my suggestion is you use the TabBar as it's intended by Apple. Don't hide it while you're presenting your CreateQuizVC, and use the TabBar to navigate between the tabs. This will help with user experience, since everybody on iOS is expecting this behaviour from a TabBar.

Changing view controller to tab bar controller

I have a tab based application with 2 tabs (HomeViewController and SettingsViewController).
On the SettingsViewController I have a button that will take the user to a third view (ChangeSomeSettingController).
On that third view, I have a TableView with this function
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
...
let settingsViewController = self.storyboard!.instantiateViewControllerWithIdentifier("SettingsViewController") as! SettingsViewController
self.presentViewController(settingsViewController, animated:true, completion:nil)
}
This will move the user back to the SettingsViewController. However, I am not able to get the tab bar to display at the bottom of the screen like it is when I first load the application.
How can I go back to the SettingsViewController and keep the tabs at the bottom of the screen?
First of all, presentViewController:animated:completion: will present SettingsViewController modally. What you need is a UINavigationController flow.
1) Add create UITabBarController
2) Select the item(UIViewController) that will has a UINavigationControler flow.
3) Delete it
4) Add UITableViewController
5) Add the new UITableViewController to the UITabBarController
6) Select the UITableViewController and embed in(Editor > Embed In > Navigation Controller) to a Navigation Controller.
7) Add a UIViewController and add a segue(Show) between UITableViewController and UIViewController
8) The result final result should be something like this:
In ChangeSomeSettingController, if you pushViewController from SettingViewController, you should call
navigationController?.popViewControllerAnimated(true)
or if you want to back to SettingViewController
//back to root view controller from navigation
navigationController?.popToRootViewControllerAnimated(true)
// you should call to tabbar view controller
UITabbarViewController *tabbarVC = (UITabbarViewController *)[UIApplication sharedApplication] delegate].window.rootViewController;
[tabBarController setSelectedIndex:1]; // 1 is index of SettingViewController
This's example for me, with your problem you can modify to adapt your requirement. hope this help for you.
This line:
self.presentViewController(settingsViewController, animated:true, completion:nil)
will not move back the user to settings VC, it will present a new instance of the settings VC, which ofcourse covers the tab bar.
You need to push the VC when you select the tabs, so you don't have to present them again if you want to go back.
If you don't want a navigation controller, then you should present the tab bar controller instead of settings vc.

Present View Controller Over current tabBarController with NavigationController

When presenting or dismissing VC, I do not want to keep hiding and showing tabBar because it creates a poor user experience. Instead, I want present the next VC straight over the tab bar such that when I dismiss the nextVC by dragging slowly from left to right, I can see the tabBar hidden behind the view (As shown in image below)
Note, my app has two tabs with two VCs(VCA,VCB) associated to it. Both VC also have navigation bar embedded. VCA segues to VCA1 and VCB segues to VCB1. At the moment, inside VCA and VCB I am calling the following function to segue with some hiding and unhiding done when viewWillappear (Code below).
self.navigationController?.showViewController(vc, sender: self)
// Inside ViewWillAppear Only reappear the tab bar if we successfully enter Discover VC (To prevent drag back half way causing tab bar to cause comment entry to be floating). This code check if we have successfully enters DiscoverVC
if let tc = transitionCoordinator() {
if tc.initiallyInteractive() == true {
tc.notifyWhenInteractionEndsUsingBlock({(context: UIViewControllerTransitionCoordinatorContext) -> Void in
if context.isCancelled() {
// do nothing!
}
else {
// not cancelled, do it
self.tabbarController.tabBar.hidden = false
}
})
} else {
// not interactive, do it
self.tabbarController.tabBar.hidden = false
}
} else {
// not interactive, do it
self.tabbarController.tabBar.hidden = false
}
----------Working solution from GOKUL-----------
Gokul's answer is close to spot on. I have played with his solution and came up with the following improvement to eliminate the need to have a redundant VC and also eliminate the initial VC being shown for a brief second before tabVC appears. But without Gokul, I would never ever come up with this!!
Additionally, Gokul's method would create a bug for me because even though I do have a initial "normal" VC as LoginVC before tabVC is shown. This loginVC is ONLY the rootVC if the user needs to login. So by setting the rootVC to tabVC in most cases, the navVC will never be registered.
The solution is to embed navigation controller and tabBar controller to one VC. But it ONLY works if the navVC is before the TabBarVC. I am not sure why but the only way that allowed me to have navVC-> tabVC-> VC1/VC2 is to embed VC1 with a navVC first than click on VC1 again to embed tabVC (It wouldn't allow me to insert one before tabVC and I also had to click the VC1 again after embedding the NavVC).
For your requirement we need to make some small changes in your given view hierarchy
Let me explain step by step,
To meet your requirement we have to add a UIViewController(let's say InitialVC) embedded with a UINavigationController and make it as initial viewcontroller.
Then add a UITabbarController with 2 VC (VCA,VCB) // IMPORTANT: Without any navigationcontroller embedded.
Add a segue between InitalVC and TabbarController with an unique identifier(ex: Initial)
In viewWillAppear of InitalVC perform segue as below (InitialVC is unnecessary to our design we are using this just to bridge navigationController and tabbarController).
self.performSegueWithIdentifier("Initial", sender: nil)
In TabbarControllerclass hide your back button, this ensures that InitialVC is unreachable.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
}
Now add a segue from a button between VCA and VCA1, thats it build and run you will see VCA1 presenting over VCA's tabbar.
What we have changed?
Instead of adding UINavigationController inside UITabbarController we have done vice versa. We can't directly add Tabbar inside navigation to do that we are using InitialVC between them.
Result:
1st way is create a image of the tabbar using UIGraphicsGetImageFromCurrentImageContext and set it on the bottom of the other view...
2nd way is show the next view in another new window that is above the tabbar, that way you wont need to hide the tabbar anymore, but seems like its in the navigation controller so this way doesnt seems available
Hiding and unhiding the tab bar is unnecessary. You only need to embed the UITabBarController inside the UINavigationController. That is, UINavigationController as the initial vc, UITabBarController as the root vc of UINavigationController.

How to do an action when tab bar item is pressed every time Swift

I am making an application with TabBar. In the Tab Bar there are 2 tabs. One of those tabs is history which is linked with a table view which shows history. I want to refresh that view every time when i click that so that i can get updated tableview. How can i do that? Thanks in advance.
You probably just need to put the reload call in the viewWillAppear of the viewController with the table you want to reload, then, every time it is presented it will reload the data.
Anyway, if you want to catch the tap on of the tabs, you need to subclass UITabBarController and then implement this method -(void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
Hi easiest solution for swift 4, to reload view by tapping again on tab bar icon.
#objc
func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController) {
if tabBarController.selectedIndex == 0 {
viewController.viewDidLoad()
}
}

iOS Disable popping to root view on pressing tab bar button

I'm looking to disable the functionality of pressing my tab bar and returning to the root view in the navigation hierarchy whilst also keeping this functionality for a button in my navigation bar.
So the only way I want the user to go back to the root view is by pressing the button in the navigation bar and not by tapping the tab bar button.
I've had a look around and tried several solutions but none seem to work as they disable the functionality for both the nav bar button and the tab bar button but not just the tab bar button.
Thanks!
A possibility would be to create a subclass of UITabBarController and to implement the UITabBarControllerDelegate protocol.
Then you could implement
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
and return NO, when the particular viewController is selected again.
A way to do this is is to save the recently selected item and to compare it with the currently selected one.
Came across this issue at the weekend. I kept finding my custom TabBarController was nil during appDelegate didfinishlaunching() method.
The way I got around it was to make my custom TabBarController a UITabBarControllerDelegate and then implemented the following delegate method withint he custom TabBarController class:
// Stops View Controllers from being dismissed when a tab is selected on the UITabBarController
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return viewController != tabBarController.selectedViewController
}

Resources