How to get the ViewController from UITabBarItem - ios

When a user taps on an item, I want to get the ViewController associated with that tab.
The TabBar delegate no longer provides the ViewController delegate. Instead, it provides a didSelectItem delegate.
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
}
How do I get the ViewController from UITabBarItem?

If you're using a UITabBarController and not just a UITabBar look into using UITabBarControllerDelegate rather than UITabBarDelegate. UITabBarControllerDelegate provides the method:
func tabBarController(_ tabBarController: UITabBarController,
didSelectViewController viewController: UIViewController)

This is a swift 4 Extension version that works for me:
import UIKit
extension UIApplication {
var visibleViewController: UIViewController? {
guard let rootViewController = keyWindow?.rootViewController else {
return nil
}
return getVisibleViewController(rootViewController)
}
private func getVisibleViewController(_ rootViewController: UIViewController) -> UIViewController? {
if let presentedViewController = rootViewController.presentedViewController {
return getVisibleViewController(presentedViewController)
}
if let navigationController = rootViewController as? UINavigationController {
return navigationController.visibleViewController
}
if let tabBarController = rootViewController as? UITabBarController {
// Uncomment the line bellow the TabBarController
//return tabBarController.selectedViewController
// uncomment the line bellow to get the visible ViewController of the TabBarController
return getVisibleViewController(tabBarController.selectedViewController!)
}
return rootViewController
}
}
This can be called as easy as this:
let visibleVC = UIApplication.shared.visibleViewController
I hope this work for you as well ;)

Related

How to update the value in a VC when a tab is selected from a UITabbarController?

I have a tabbar controller. When the user clicks on one of the tab bar buttons, I need to update a value in the UIPageViewController that is in the target view controller.
I am trying to use a delegate to inform a UIPageViewController which tab bar button was clicked:
protocol PlanTypeDelegate {
func setIntro(thisFlow planType: UITabBarItem)
}
class NewTabBarController: UITabBarController {
var planTypeDelegate : PlanTypeDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// create and handle tab bar button actions
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
planTypeDelegate?.setIntro(thisFlow: item)
}
In my UIPageController I have the following:
class IntroPageController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let tabbar = self.parent as? NewTabBarController() else { return }
tabbar.delegate = self
}
}
extension IntroPageController : PlanTypeDelegate {
func setIntro(thisFlow planType: UITabBarItem) {
print("this item:\(planType)")
}
}
Instead I get this error message:
I am new to passing data between VCs so I have no idea how to go about handling this scenario.
EDIT
Same error after update
You can Achieve it like this.. without Delegate ... write setIntro method in IntroPageController i hope it will solve your Issue
class NewTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
}
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
if let controller = viewController as? IntroPageController {
controller.setIntro(thisFlow: tabBarController.tabBarItem)
}
return true
}
You can also achieve it through Protocol for that write... All controllers who confirm PlanTypeDelegate can perform action against this method
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
if let navController = viewController as? UINavigationController {
if let myViewController = navController.topViewController , let homeController = myViewController as? PlanTypeDelegate {
homeController.setIntro(thisFlow: tabBarController.tabBarItem)
}
} else if let controller = viewController as? PlanTypeDelegate {
controller.setIntro(thisFlow: tabBarController.tabBarItem)
}
return true
}

swift4: how to check if user logged befor tab bar item clicked

This question has been asked before and has been answered, my question is not unique but there must be something missing. I'm simply trying to check if user logged in to app before as his data stored in UserDefaults but it doesn't work for me, this is the class of my TabBarViewController
class TabViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// UITabBarDelegate
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print("Selected item")
}
// UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if(viewController is MessagesViewController) {
print("trueee")
if(UserStorage.id == "") {
Toast.toast(messsage: "not loggoed user", view: self.view)
}
} else {
print("faaaaaaaalse")
}
print("Selected view controller")
}
}
i want to check if user open MessageViewController then if user is logged in to print something but it always print faaaaaaaalse
note: Toast.toast() is a function i created to show toast
and UserStorage.id returns user id stored in USerDefaults
this is image which shows my structure:
what should I do ?
Just your tab bar’s root controllers are 2 navigaton controllers, not MessageViewController. Firstly with tabBarController you have to find navigationController which contains your MessageViewController than in this navigation find the needed ViewController.
So I have the solution for you:
import UIKit
class TabbarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// for tab bar initialization
if let viewControllers = self.viewControllers,
viewControllers.count >= 1,
// the index of viewController is 0 here, but if your tab bar's started controller is not 0 you can set yours
let navigationController = viewControllers[0] as? UINavigationController {
for controller in navigationController.viewControllers {
if let messagesViewController = controller as? MessagesViewController {
doWithMessagesViewControllerWhatYouWant(_viewController: messagesViewController)
}
}
}
}
// UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if let navigationController = viewController as? UINavigationController{
for controller in navigationController.viewControllers {
if let messagesViewController = controller as? MessagesViewController {
doWithMessagesViewControllerWhatYouWant(_viewController: messagesViewController)
}
}
}
}
private func doWithMessagesViewControllerWhatYouWant(_viewController: MessagesViewController) {
print("do some operations with messagesViewController")
if(UserStorage.id == "") {
Toast.toast(messsage: "not loggoed user", view: self.view)
}
}
}

How to set viewcontroller of UITabbarItem to default viewcontroller when switching tabs?

I have a UITabBarController. It has 4 tabBarItem.
Suppose I'm at 1st tabBarItem defaultViewController(1) and I
went to another ViewController(2) which is shown after some
actions in first defaultViewController(1).
Then I switched to 2nd tabBarItem defaultViewcontroller(2).
Again I switched back to 1st tabBarItem, it shows ViewController(2).
I want to show defaultViewController(1). How can I achieve this using swift 4.
defaultViewController(1) and defaultViewController(2) are the default ViewController for 1st and 2nd TabBarItem respectively.***
Let's say you have UINavigationController inside each tab of UITabBarController, and defaultViewController(1) is the rootViewController of your first tab, inside that there is a button that navigate to ViewController(2).
For this first of all, let's create generic solution. Create UIApplication Extension like this,
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
return controller
}
}
Implement UITabBarControllerDelegate in AppDelegate and do below code,
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if tabBarController.selectedIndex == 0 {
UIApplication.topViewController()?.navigationController?.popToRootViewController(animated: false)
}
}
In above code, I have taken tabBarController.selectedIndex to 0, you can make it different depending on your requirement.
Let me know in case of any queries.
Extend a subclass of UITabbarController and use it as your tabbar's class. In that implement UITabBarControllerDelegate, didSelect and use popToRootViewController to pop to your defaultViewController.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController){
if viewController is UINavigationController {
//when you have `UINavigationController`
let rootNavigationController = viewController as! UINavigationController
rootNavigationController.popToRootViewController(animated: false)
} else {
//when you don't have `UINavigationController` then dismiss all viewcontroller that was presented.
let rootViewController = viewController
if rootViewController.presentingViewController != nil {
rootViewController.dismiss(animated: false, completion: nil)
}
}
}
Note: add self.delegate = self to conform the protocol inside viewDidLoad method.

Present a View Controller modally from a tab bar controller

I want to build a view with a camera. Something just like Instagram where there is a button in the middle that the user can click and the camera view shows up.
I implemented a code for the TabViewController in the AppDelegate but nothing happens, no animation or Presentation of the new ViewController.
Here is my AppDelegate:
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate {
var window: UIWindow?
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: ViewController) -> Bool {
if viewController is ViewController {
let storyboard = UIStoryboard(name: "Main.storyboard", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "cameraVC") as? ViewController {
controller.modalPresentationStyle = .fullScreen
tabBarController.present(controller, animated: true, completion: nil)
}
return false
}
return true
}
Here is my Storyboard:
Any ideas?
I suggest to create a custom class for your TabBarController, then assign the delegate to that.
You can either assign and check the restorationIdentifier of the view controller, or do a type check. I usually use storyboard identifier as the restoration identifier of the view controller(s).
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let identifier = viewController.restorationIdentifier, identifier == "cameraVC" {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "cameraVC") as! CameraViewController
present(vc, animated: true, completion: nil)
return false
}
return true
}
}
Here's a sample you can play with:
https://gist.github.com/emrekyv/3343aa40c24d7e54244dc09ba0cd95df
I just tried and It worked perfectly for me:
Create a Custom class for your TabBarController and assign it to your Controller in Storyboard.
After that override didSelect of tabBarController and write your presentation code there :
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if let controller = self.viewControllers?[self.selectedIndex] as? ViewController {
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil
}
}
Hope it helps!!

How to reset tab (or tab controller) of UITabBarController when user select tab?

Swift: I have UITabBarController, with 8 tabs. When user select any tab including more tab, I want reset content of selected tab by Popping to rootView controller?
How could this be done?
I tried to reset navigation controllers in below methods, It works for tabs which are visible at bottom but it doesn't work for More tab.
tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem)
A clean way to do this:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let index = tabBarController.selectedIndex
if index == NSNotFound || index > 4 {
tabBarController.moreNavigationController.popToRootViewController(animated: false)
return
}
let navController = tabBarController.viewControllers?[tabBarController.selectedIndex] as? UINavigationController
navController?.popToRootViewController(animated: false)
}
Create a custom class for your UITabViewContoller, set a delegate and put there that piece of code.
You can create a UITabBarController class for your tabBar and in that class ovveride the tabBar didSelect method from their on select of any tab you can popViewController
class TabBarViewController: UITabBarController {
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
let navigationController1 = self.viewControllers![0] as? UINavigationController
navigationController1!.popViewController(animated: false)
let navigationController2 = self.viewControllers![1] as? UINavigationController
navigationController2!.popToRootViewController(animated: false)
let navigationController3 = self.viewControllers![2] as? UINavigationController
navigationController3!.popToRootViewController(animated: false)
let navigationController4 = self.viewControllers![3] as? UINavigationController
navigationController4!.popToRootViewController(animated: false)
}
}
Use viewControllers property of UITabbarController. viewControlers is an array of UIViewController for UITabbarController. Exchange/Move position of view controllers upon tabbar(controller) selection.
Use tabbar(controller)'s delegate method to hanlde these operations, like:
Swift 3
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if var viewcontrollers = tabBarController.viewControllers {
viewcontrollers.remove(at: tabBarController.selectedIndex)
viewcontrollers.insert(viewController, at: 0)
tabBarController.viewControllers = viewControllers
}
}
You can also use this delegate method, if you have an access to tabbarController where you have implemented.
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
// Write a similar code to exchange/move view controllers using tabbarController instance.
}
This worked for me.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let navigationController = tabBarController.viewControllers?[tabBarController.selectedIndex] as? UINavigationController else {
return
}
navigationController.popToRootViewController(animated: false)
}
To fully support More, create a custom class like below:
class Main_TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.customizableViewControllers = [] //remove Edit from More (OPTIONAL)
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if let index = tabBar.items?.firstIndex(of: item) {
if let count = viewControllers?.count, count > 5, index == 4 {
DispatchQueue.main.async {
self.moreNavigationController.popToRootViewController(animated: false)
}
} else if let vcs = viewControllers, let vc = vcs[index] as? UINavigationController {
DispatchQueue.main.async {
vc.popToRootViewController(animated: false)
}
}
}
}
}

Resources