I have a tab bar controller with 3 tab bar buttons. At the moment it looks like this:
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let firstTabBarController = FirstController()
let firstTabBarNavigationController = UINavigationController(rootViewController: firstTabBarController)
firstTabBarNavigationController.tabBarItem.title = "First Tab"
let secondTabBarController = SecondController()
let secondTabBarNavigationController = UINavigationController(rootViewController: secondTabBarController)
secondTabBarController.tabBarItem.title = "Second Tab"
let thirdTabBarController = ThirdController()
let thirdTabBarNavigationController = UINavigationController(rootViewController: thirdTabBarController)
thirdTabBarNavigationController.tabBarItem.title = "Third Tab"
viewControllers = [firstTabBarNavigationController, secondTabBarNavigationController, thirdTabBarNavigationController]
}
}
Right now, with the code above, all the view controllers are sitting inside the CustomTabBarController
I want the the middle tab bar button, the secondTabBarNavigationController to present a view controller, specifically the UIImagePickerController for a user to select an image, similar to Instagram.
How can this be achieved? I am not using storyboards
Just implement the func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool delegate method of the tabBar controller.
When the desired tab is pressed, present a controller and return false, otherwise return true.
I would also associate an empty UIViewController instance to the tab.
Ex.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if (tab for this controller is equal to the expected tab) {
// present you controller
return false
} else {
return true
}
}
Related
I have a Swift application with 4 bottom tabs. In the home tab I have a video running. In the 4th tab I have a favorites list.
When the user changes tabs, if he's on the home screen, the video should stop. Also, when the user taps on the 4th tab, the favorites list should update so that the user can see the recent additions.
I have the following on the home tab view controller:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
print("TAB CHANGED")
if let jwp = jwPlayer {
jwp.stop()
}
}
and this on the favorites tab view controller:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 3 {
let userId = UserDefaults.standard.integer(forKey: "userId")
favoritesVC.updateData(with: userId)
}
}
Both conform to UITabBarControllerDelegate. Both include:
self.tabBarController?.delegate = self
When I launch the app, the video starts playing, I tap on any tab and the video stops. No matter which tab I tap on I see the message "TAB CHANGED". But as soon as I tap on the favorites tab and I move to another, I stop seeing the "TAB CHANGED" message. If I then move to the home screen and play the video and then move to a different tab, the video no longer stops.
The didSelect on the 4th tab is cancelling the didSelect on the 1st tab.
How can I get both of them to work? I have placed them on both view controllers because on the first one I need to reference the video and on the 2nd one I need to reference the list view controller (the 4th view controller actually has two top tabs which switch between a favorites vc and a downloads vc).
UPDATE:
I moved the delegate to the TabBarController subclass. I added the following:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
let hvc = HomeViewController()
if tabBarIndex != 0 {
hvc.stopPlayer()
}
}
I added
delegate = self
and
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
In the HomeViewController I added this:
func stopPlayer() {
print("TAB CHANGED")
if let jwp = jwPlayer {
jwp.stop()
}
}
however when I change tabs, jwPlayer is always nil.
It seems like you are handling the UITabBarControllerDelegate calbacks in multiple places and for that to happen, you are also changing following multiple times -
self.tabBarController?.delegate = self
Here's what you should do -
Handle UITabBarControllerDelegate in one place.
Dispatch the necessary work calls from there to relevant screens.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if tabBarIndex == 0 {
homeVC.startPlayer()
}
else {
homeVC.stopPlayer()
if tabBarIndex == 3 {
let userId = UserDefaults.standard.integer(forKey: "userId")
favoritesVC.updateData(with: userId)
}
}
}
This can be handled anywhere, inside your TabBarController subclass if you have one, or any other object that is guaranteed to exist for the application lifetime.
TabBarController’s delegate does not have to be either of the view controllers. It could be some other object (not even a view controller) that could hold weak references to both view controllers, for example, or post a notification etc
I want to implement a scrollToTop method on all of my viewControllers in my UITabBarController. The following is a method in the UITabBarControllerDelegate and triggers, when I select a tab.
The problem is, that I only want to scroll to the top of the viewController, when the viewController is active. So that the user can switch tabs without losing the scroll position, but when he touches the tab in the tabBar of the currently active tab, it should scroll to the top.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if viewControllerThatIsCurrentlyActiveInTabBar == viewController {
scrollToTop()
}
}
Basically, I need that condition of the if statement above.
I tried: viewController.isViewLoaded, tabBarController.selectedViewController == viewController, viewController.isBeingPresented. None of those conditions worked. It would either not trigger scrollToTop() or it would trigger always so that you lose the scroll position when you change tabs because it would immediately scroll to the top.
You need to make a code in should select instead of didselect. As it is unable to find the previous controller after selection. below is the example code for it.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if tabBarController.selectedViewController == viewController {
print("Same viewcontroller")
}
return true
}
Can you use below extension for getting top viewcontroller of tabbarcontroller.
extension UIViewController {
var top: UIViewController? {
if let controller = self as? UINavigationController {
return controller.topViewController?.top
}
if let controller = self as? UISplitViewController {
return controller.viewControllers.last?.top
}
if let controller = self as? UITabBarController {
return controller.selectedViewController?.top
}
if let controller = presentedViewController {
return controller.top
}
return self
}
}
You can use above extension below
if let rootViewController = UIApplication.top() {
//do with Active view controller
}
So I have a UITabBarController with two View Controllers Embedded into it. I implemented the did select tab bar method where when the user selects a tab, it passes a value into that controller. However when the tabBarController loads for the first time, the did select method is not called even though I have
self.selectedIndex = 0
Which selects the first index. Basically I am just trying to automatically select the first tab Bar Item when the view loads, and have it call the didSelectTabBarItem method
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
}
The question is similar to this one.
tabBarController didSelect does not get called
optional func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController)
In iOS v3.0 and later, the tab bar controller calls this method regardless of whether the selected view controller changed. In addition, it is called only in response to user taps in the tab bar and is not called when your code changes the tab bar contents programmatically.
Copy this in your code
class HomeTabBarVC: UITabBarController {
var isIpad = false
let button = UIButton.init(type: .custom)
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
APPDELEGATE.tabBar = self
}
}
extension HomeTabBarVC : UITabBarControllerDelegate {
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print("the last selected index is : \(selectedIndex)")
APPDELEGATE.tabBarLastSelectedIndex = selectedIndex
print("the current selected index is : \(String(describing: tabBar.items?.index(of: item)))")
APPDELEGATE.tabBarCurrentSelectedIndex = tabBar.items?.index(of: item) ?? 0
}
}
In appdelegate declare this variable,
var tabBar : UITabBarController?
I have a UITabBarController with 4 viewControllers setup.
One of the controller has a button that present another controller (wrapped on UINavigationController) with the following setup:
self.definesPresentationContext = true
navController.modalPresentationStyle = .overCurrentContext
navController.modalTransitionStyle = .crossDissolve
self.present(navController, animated: true)
Until this point is working fine.
Now if I switch to another tab (while the previous modal is open), and return again to the tab that presented the modal (The screen is still there, that's ok). Then if i close the modal (from a Button), the modal is dismissed but the controller view's has gone (white), then if I switch to another tab and return to the tab again, the view load correctly.
Note: For this case, I need overCurrentContext, don't want to block UITabBarController (with fullScreen).. Also try with .currentContext, custom
If this is the same bug that I demonstrate here, the workaround I give is to prevent the user from switching to another tab while this tab is showing the presented view controller:
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
}
extension FirstViewController : UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return self.presentedViewController == nil
}
}
Normally when a UINavigationController is placed in a UITabBarController the navigation controller pops to root with the respective tab it is in is double tapped. How do I achieve the same effect with a UISplitViewController is between the tab bar controller and the navigation controller? Ideally it would recurse through the view controller's child view controllers and calling popToRootViewController on all navigation controllers that it finds. Do I have to add my own gesture recognizer to the tab bar since it doesn't look like there is a hook for knowing when a user has double tapped a tab?
In Swift 4, it can go something like this:
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
private var shouldSelectIndex = -1
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
shouldSelectIndex = tabBarController.selectedIndex
return true
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if shouldSelectIndex == tabBarController.selectedIndex {
if let splitViewController = viewController as? UISplitViewController {
if let navController = splitViewController.viewControllers[0] as? UINavigationController {
navController.popToRootViewController(animated: true)
}
}
}
}
}
Instead of setting up a UIGestureRecognizer I simply keep track of the selected index in shouldSelectViewController and popped to root on my master navigation controller in didSelectViewController if the old selected index was the same as the new one.