Swift initialize tabBarController without presenting it - ios

The initial view controller is the tab bar controller. The tab bar is created programmatically.
Problem:
I need to load the TabBarViewController without presenting it if the boolean value loggedIn == false.
On older devices, this code works fine to never show the TabBarViewController if the user is not logged in.
However, on newer/faster devices the tab bar flashes before presenting the LogInViewController().
final class TabBarViewController: UITabBarController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureTabs()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if(!loggedIn){
let loginVC = LogInViewController()
loginVC.isModalInPresentation = true
loginVC.modalPresentationStyle = .fullScreen
present(loginVC, animated: false)
}
}
}
What is the best work around to still load the TabBarViewController but not show it if I need to present a new VC.

Related

hidesBottomBarWhenPushed hides TabBar forever

I use Xcode 11.2 and the project minimum iOS deployment target is iOS 12.4.
I have a TabBarController on root page and on one of the tabs I have FirstViewController. When I push SecondViewController from FirstViewController, I want the tab bar to be hidden. I used hidesBottomBarWhenPushed property to hide the tab bar.
The tab bar is hidden when I push SecondViewController but when I pop the SecondViewController and move back to FirstViewController, the tab bar is still hidden.
I tried several ways to set hidesBottomBarWhenPushed to false when moving back to FirstViewController but none of the tries worked.
How can I re display tab bar when popped back to FirstViewController?
class FirstViewController: UIViewController {
#IBAction func buttonTap(_ sender: Any) {
let vc2 = SecondViewController()
// Set to Hide TabBar
hidesBottomBarWhenPushed = true
navigationController?.pushViewController(vc2, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// This Does Not Work
hidesBottomBarWhenPushed = false
}
}
class SecondViewController: UIViewController {
/*
All The Followings Does Not Work
*/
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
hidesBottomBarWhenPushed = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
hidesBottomBarWhenPushed = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
hidesBottomBarWhenPushed = false
}
}
The key was to set hidesBottomBarWhenPushed to true from outside of the SecondViewController.
The code below was all I needed to write.
class FirstViewController {
func pushSecondViewController {
let vc = SecondViewController()
vc.hidesBottomBarWhenPushed = true // <- Here
navigationController?.push
navigationController?.pushViewController(vc, animated: true)
}
}

Resetting the navigation stack on a UITabBarController doesn't work

I'm trying to reset the navigation stack on a subclass of UITabViewController embedded in a UINavigationController but it doesn't work.
My navigation stack, which I create programmatically, is like this:
UINavigationController => ControllerA (a subclass of UIViewController) =>
ControllerB (a subclass of UIViewController) => ControllerC (a
subclass of UITabBarController).
When users press on the "Back" button or swipe back from ControllerC, the app should go back to ControllerA, not ControllerB.
Usually, when I want to reset the navigation stack, I do this in the Controller's viewDidLoad() method:
override func viewDidLoad() {
super.viewDidLoad()
// usually work, but not in a subclass of UITabBarController as self.navigationController is nil
if let navigationController = self.navigationController {
// keep only the root controller (0) and the current controller
navigationController.viewControllers = [navigationController.viewControllers[0], self]
}
}
but this doesn't work in ControllerC (the subclass of UITabViewController) as self.navigationController is nil.
If I do this instead (still in ControllerC's viewDidLoad() method):
/// ControllerC's viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
if let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
// keep only the root controller (0) and the current controller
navigationController.viewControllers = [navigationController.viewControllers[0], self]
}
}
This works, but then there is no animation between ControllerB and ControllerC when I do:
controllerB.navigationController?.pushViewController(ControllerC(), animated: true)
I also tried to override ControllerC's viewWillDisappear() method:
/// ControllerC's viewWillDisappear
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
if let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
navigationController.popToRootViewController(animated: true)
}
}
This works, but ControllerB is briefly visible before ControllerA is shown.
Any help would be greatly appreciated!
In the ControllerC instead of trying to override viewWillDisappear() method you can override viewDidAppear() like that:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let navC = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
// keep only the root controller (0) and the current controller
navC.viewControllers = [navC.viewControllers[0], self]
}
}
And ControllerB won’t be briefly visible before ControllerA when you navigate backwards.

How can I hide the tab bar for a single view controller in a navigation controller stack

I have a UITabBarController. One of the tabs contains a UINavigationController.
I'd like to push a view controller onto the navigation stack and hide the tab bar on that view controller. I can do this easily with:
toVC.tabBarController?.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(toVC, animated: true)
or doing it in the storyboard:
The problem is, this hides the tab bar for any subsequent view controllers I push onto the stack. I'd like to simply hide the tab bar for this one view controller and show it for all other view controllers before and after it.
There is a workaround. It works the way it is presented on gif below.
For each UIViewController that is pushed into the UINavigationController stack I override the hidesBottomBarWhenPushed property this way:
override var hidesBottomBarWhenPushed: Bool {
get {
switch navigationController?.topViewController {
case .some(let controller):
switch controller == self {
case true:
return super.hidesBottomBarWhenPushed
case false:
return false
}
default:
return super.hidesBottomBarWhenPushed
}
}
set {
super.hidesBottomBarWhenPushed = newValue
}
}
The first switch checks whether this controller belongs to some UINavigationController stack. The second switch checks whether current top UIViewController of UINavigationController stack is self.
Hope it will work in your case. Happy coding (^
If you hide on the storyboard then by this property your tab bar will hide for all the view controllers. So you can manage this by code.
You can do this programmatically by just writing one line of code in ViewDidLoad() or ViewWillAppear() method
For Swift 3:-
self.tabBarController?.tabBar.isHidden = true
And where you want to unhide the tab bar just write the following code in ViewDidLoad () or ViewWillAppear() method
self.tabBarController?.tabBar.isHidden = false
Try this in the view controller you want to hide the tab bar in:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
}
And this in the view controllers before and after the one you want to hide the tab bar in:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = false
}
EDIT:
Fully implemented example:
class ViewController1: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = false
}
}
class ViewController2: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
}
}
class ViewController3: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = false
}
}

Bottom tabBar controller not hiding when I pass to another viewController

I am making a project where the first MainPage is TabBarController(MainTabController). But then I am passing to another Viewcontroller there is one more tabbetViewControllers(secondTabbedController). And now When I pass to secondTabbedController the tabs of MainTabController are not hiding. There Should be secondTabBarController Items but there tab items of first(MainTabBarController). I guess that it is because of the navigationController and If I delete it it is fixes. But I need this NavigationController. How to fix it ?
This is ArticlesViewController that You can find in first image:
Here is the solution:
write this code in first viewcontroller
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// this will show the tabbar when come back to first viewcontroller
self.tabBarController?.tabBar.isHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// this will hide the tabbar when moved next viewcontroller
self.tabBarController?.tabBar.isHidden = true
}
Just true this "hidesBottomBarWhenPushed" when you are pushing view controller
let objViewController: ProductDetailsViewController? = UIStoryboard.mainStoryboard().instantiateViewController(withIdentifier: "ProductDetailsViewController") as? ProductDetailsViewController
objViewController?.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(objViewController ?? UIViewController(), animated: true)
And you can also set this in ViewController XIB "Attribute Inspector" by just clicking "Hide Bottom Bar on Push".

Does 'pushViewController' Prevent the ViewController from Calling deinit?

I have several view controllers (UIViewController). Each view controller has its own storyboard. And I want the app to call deinit when it transitions from one view controller to another. But it won't.
The app starts with HomeViewController. And it will transition to SelectViewController when the user taps a button (UIButton).
class HomeViewController: BasicViewController {
#IBAction func selectTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name:"Select",bundle:nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SelectView") as! UINavigationController
let viewController = controller.topViewController as! SelectViewController
self.navigationController?.pushViewController(viewController, animated: true)
}
deinit {
print("HOME HAS BEEN REMOVED FROM MEMORY")
}
}
The app never calls deinit when it leaves HomeViewController. I wonder if that's because it's pushing a view controller? If I set the view controller as follows, the app will crash. What am I doing wrong? I have never done it with different storyboards. Thanks.
class HomeViewController: BasicViewController {
#IBAction func selectTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name:"Select",bundle:nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "SelectView") as! SelectViewController
self.navigationController?.setViewControllers([viewController], animated: true)
}
}
UPDATE 1
I guess it's just the following.
#IBAction func selectTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name:"Select",bundle:nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SelectView") as! UINavigationController
self.present(controller, animated: true, completion: nil)
}
UPDATE 2
I also clear the view controller stack when SelectViewController appears as follows.
override func viewWillAppear(_ animated:Bool) {
super.viewWillAppear(animated)
var navigationArray = self.navigationController?.viewControllers
navigationArray?.removeAll()
}
Actually, It is not about storyboards. As per the Apple's documentation, "deinit" method gets automatically called when an object is deallocated from memory, not when a view controller is pushed from current one.
When you push "SelectViewController" from "HomeViewController", instance of "HomeViewController" does not deallocated from memory, hence deinit method does not called.
Here is a link to Apple's Documentation for Deinitialization. Alternatively, you can use "viewDidDisappear" method of the view controller to perform an operation if it satisfies your need.
From your code snippet, I understand that you expect deinit is called after pushViewController. But, by calling pushViewController, your navigation controller pushes your HomeViewContoller to navigation stack, which means navigation controller holds strong reference to your HomeViewController object. So, deinit is not called. If you don't need HomeViewController, you have to manually remove it from navigation stack w/in SelectViewController.
See this how. How to remove a specific view controller from uinavigationcontroller stack?
In SelectViewController,
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let allControllers = NSMutableArray(array: navigationController!.viewControllers)
allControllers.removeObject(at: allControllers.count - 2)
self.navigationController!.setViewControllers(allControllers as [AnyObject] as! [UIViewController], animated: false)
}

Resources