I have created a tab bar with 5 tabs. we want to create a feature that we can enable/disable some tabs of tab bar programmatically so that user will not able to click on it.
We have used default tab bar controller and we are using swift 3. Does anyone have an idea how to manage this stuff?
I have tried many ways but it seems that it's not possible to restrict the user to click on the tab.
Please let me know if anyone has faced and solved this issue.
let tabBarControllerItems = self.tabBarController?.tabBar.items
if let tabArray = tabBarControllerItems {
tabBarItem1 = tabArray[0]
tabBarItem2 = tabArray[1]
tabBarItem1.isEnabled = false
tabBarItem2.isEnabled = false
}
Just put the block of code above in the viewDidLoad() method for starters and don't forget to create the tabBarItem variables
Try this in your viewWillAppear() method :
if let arrayOfTabBarItems = tabBarViewController.tabBar.items as! AnyObject as? NSArray,tabBarItem = arrayOfTabBarItems[2] as? UITabBarItem {
tabBarItem.enabled = false
}
Note :The above code will disable your third tab from clicking, to disable any other, just change the index in the arrayOfTabBarItems
Swift 3 xcode 8.3.3
I am making a demo App For your Problem. This is the code for firstViewController in TabBar ViewController.
class firstViewController: UIViewController ,UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController.isKind(of: secondViewController.self as AnyClass) {
return true
}
if viewController.isKind(of: thirdViewController.self as AnyClass) {
return false
}
}
}
In That Demo SecondViewController is Click and open ViewController. But thirdViewController is not clicked.
Just create a customTabBarController class and put the bellow code on viewDidLoad().
if let arrayOfTabBarItems = self.tabBar.items as AnyObject as? NSArray,let tabBarItem = arrayOfTabBarItems[3] as? UITabBarItem {
tabBarItem.isEnabled = false
}
so you can change arrayOfTabBarItems index based on your requirment.
Related
I have a Tabbed App with two tabs... the first tab has the main Action, and the second has Settings that can be updated. I am trying to pass some variables data from Settings to the Action tab. Based on some suggestions, I have used the following code for the Update button:
#IBAction func updateBut(_ sender: Any) {
let myVC = self.storyboard?.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
myVC.totTime = totalTime
myVC.timeInt = intTime
self.present(myVC, animated: true, completion: nil)
}
The data does pass to the first view controller, however, the tabs have disappeared on this view now. So, how can I get the tabs back on the screen? I am quite the beginner to any form of app development, and am just trying to learn by doing... the Tabbed App has been created using one of the Xcode New Project templates. Thanks.
try this way
(self.tabBarController?.viewControllers?[0] as! FirstViewController).totTime = totalTime
(self.tabBarController?.viewControllers?[0] as! FirstViewController).timeInt = intTime
self.tabBarController?.selectedIndex = 0
self.tabBarController?.tabBar.isHidden = false try this
A much better way to pass data by using protocols, you can define a protocol like below
protocol PassDataDelegate{
func updateFirstVc(totalTime:String)
}
and in your SecondViewController class have a delegate property like below
class SecondViewController:UIViewController{
myDelegate:PassDataDelegate?//declaration part
#IBAction func updateBut(_ sender: Any) {
myDelegate.updateFirstVc(totalTime:totalTime)
}
}
And finally in your UITabController class implement the protocol
class myTabController:UITabController,PassDataDelegate{
var firstController:FirstViewController? //declaration part
var secondController:SecondViewController?
override func viewDidLoad(){
super.viewDidLoad()
//initialize your view controller here
self.firstViewController = FirstViewController()
self.secondViewController = SecondViewController()
self.secondViewController.myDelegate = self //assign delegate to second vc
self.viewcontrollers = [firstController, secondController]
}
updateFirstVc(totalTime:totalTime){
self.firstViewController?.totTime = totalTime
self.selectedIndex = 0// here change the tab to first vc if you want to switch the tab after passing data
}
}
I am new to iOS correct me If I am wrong,
The reason why the TabBar is not visible since you are instantiating new FirstViewController which is present on top of your TabBar.
TabBar by default does this Job or Add the new viewController to the TabBar Stack.
tabBarController.viewControllers.append(myVC)
For passing the data TabBar holds the reference of all its ViewControllers. So you can set or get in each other ViewControllers like this
var yourData{
set{
let yourVC = self.tabBarController?.viewController[0] as? FirstViewController ?? ErrorClass
yourVC.dataObj = newValue
}
get{
let yourVC = self.tabBarController?.viewController[0] as? FirstViewController ?? ErrorClass
return yourVC.dataObj
}
I have an app with a tabbar and a navbar.
I have a BaseVC and a DetailVC. I'm pushing DetailVC from BaseVC. I want the tabbar to be under the pushed VC DetailVC. I'm using hidesBottomBarWhenPushed to achieve it. It works great, but for some reason while it's animating the push the tabbar is still visible and just when the animation ends the tabbar is hidden. I want it to be under the pushed VC in the animation too.
My code is:
self.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(detailVC, animated: true)
self.hidesBottomBarWhenPushed = false
And the result (the bug) is this:
Anyone has an idea why the tabbar "jumps"? Thank you!
Having looked at the project in question I have found one way to make it work:
Remove the viewWillLayoutSubviews from the TabBarViewController so that it is not determining the height of the tab bar anymore and thus not stopping the animation working correctly.
Create a new swift file called MyTabBar (or whatever you want) and put this in it:
import UIKit
class MyTabBar: UITabBar {
var tabBarHeight: CGFloat = 100
override func sizeThatFits(_ size: CGSize) -> CGSize {
let superSize = super.sizeThatFits(size)
return CGSize(width: superSize.width, height: self.tabBarHeight)
}
}
Create a storyboard called TabBarStoryboard (or whatever). It's not going to be used for anything other then to hold a UITabBarController which you later create.
In the storyboard set the class type of the UITabBarController to your class of TabBarViewController so it gets the correct class when instantiated.
In the storyboard set the class type of the UITabBar that belongs to the UITabBarController to MyTabBar so that it too is the correct class when instantiated.
In your RootViewController replace this:
fileprivate let tabBarViewController = TabBarViewController()
with this:
fileprivate lazy var tabBarViewController: TabBarViewController = {
let storyboard = UIStoryboard(name: "TabBarStoryboard", bundle: nil)
return storyboard.instantiateViewController(withIdentifier: "MyTabBarController") as! TabBarViewController
}()
In your TabBarViewController add this to the end of the viewDidLoad to set the height of the tab bar:
if let tabBar = self.tabBar as? MyTabBar {
tabBar.tabBarHeight = self.tabBarHeight
}
Now if you get all that correct you should have a tab bar the size you want and the animation should work correctly because the height of tab bar is not longer controlled by the viewDidLayoutSubviews method.
I had to use a storyboard to hold the basic UITabBarController because I couldn't find a way to set the class of its UITabBar property otherwise (if anyone knows a way add a comment.
In case this is difficult to follow I have uploaded my version of your project to dropbox and this is the link: PlayWiz-NewVersion.zip. Be careful as it will unzip to the same directory structure so extract it to a different folder than the original otherwise you will lose the original.
That method appears to work correctly for me and I see no reason for there to be any problem but test it thoroughly first.
I have a simpler variation of the above example (cheers by the way)
I pasted everything in viewDidLoad, but you can write it prettier.
class TabBarController: UITabBarController {
override func viewDidLoad() {
// create the normal buttons (controllers)
let viewControllers = [UINavigationController(rootViewController: firstButton), UINavigationController(rootViewController: secontButton)]
self.viewControllers = viewControllers
// create the middle rounded button
self.tabBar.addSubview(addItemButton)
// setup constraints
addItemButton.widthAnchor.constraint(equalToConstant: 64).isActive = true
addItemButton.heightAnchor.constraint(equalToConstant: 64).isActive = true
tabBar.centerXAnchor.constraint(equalTo: self.addItemButton.centerXAnchor).isActive = true
tabBar.topAnchor.constraint(equalTo: self.addItemButton.centerYAnchor, constant: -8).isActive = true
}
extension UITabBar {
// fix clicking the (+) external to the tabbar bounds
override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if (!self.clipsToBounds && !self.isHidden && self.alpha > 0.0) {
let subviews = self.subviews.reversed()
for member in subviews {
let subPoint = member.convert(point, from: self)
if let result:UIView = member.hitTest(subPoint, with:event) {
return result;
}
}
}
return super.hitTest(point, with: event)
}
// this fixes the "jumping" tabBar when using the "hidesBottomBarWhenPushed = true"
override open func sizeThatFits(_ size: CGSize) -> CGSize {
let superSize = super.sizeThatFits(size)
return CGSize(width: superSize.width, height: 85)
}
}
Now, just call hidesBottomBarWhenPushed = true and push the desired view controller.
My App has a TabBarViewController containing 4 tabs. One of the tabs is Settings which I want to move to a separate storyboard. If I am only consider iOS 9 and above as my deployment target, then I can just refactor the SettingsTab using Storyboard Reference. However I want to target iOS 8 as well. Since Storyboard Reference doesn't support Relationship Segue, I can't rely on it in this case.
So in the main storyboard which contains the TabBarViewController, I keep a dummy SettingsTabViewController as an empty placeholder. And in the function "viewWillAppear" in its class file, I push the view to the real SettingsTabViewController in the Settings.storyboard. This works fine. But the problem is if I keep tabbing the Settings tab, the empty placeholder view controller will show up for a short time and then goes back to the real Settings view.
I tried to implement this delegate to lock the Settings tab:
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
return viewController != tabBarController.selectedViewController
}
However, the other three tabs were locked too after I implemented this delegate.
Is it possible to just lock the Settings tab without locking other three tabs? And in which view controller exactly should I implement this delegate?
Yes, it's possible. You need to check the index;
with the following code not only you can prevent locking other tabs, but also you still have tap on tab goto root view controller feature.
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
let tappedTabIndex = viewControllers?.indexOf(viewController)
let settingsTabIndex = 3 //change the index
if tappedTabIndex == settingsTabIndex && selectedIndex == settingsTabIndex {
guard let navVC = viewController as? UINavigationController else { return true }
guard navVC.viewControllers.count > 1 else { return true }
let firstRealVC = navVC.viewControllers[1]
navVC.popToViewController(firstRealVC, animated: true)
return false
}
return true
}
.
This answers your question, but still you would have the settingsVC showing up for a moment. To avoid this you simply need to turn off the animation while you're pushing it. so you need to override viewWillAppear in the following way.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let theVC = storyboard?.instantiateViewControllerWithIdentifier("theVC") {
navigationController?.pushViewController(theVC, animated: false)
}
}
after adding above code you still would see a back button in your real first viewController. You can hide it:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true
}
I am implementing an iOS App with UITabBar with UINavigationViewController in Swift. Now facing an issue,
If I select first tab, I can see 'A' ViewController, and on click of any contents of 'A', I redirect to 'B' UINavigationViewController, Now If I click on Second tab, and then again Clicks the first tab, It is showing 'B' NavigationViewController. Expected is, It should display 'A' ViewController. How to achieve that?
Try implementing didSelectViewController delegate and then on selection of 'A ViewController' index redirect to root viewcontroller.
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
let index : Int = (tabBarController.viewControllers?.indexOf(viewController))!
if index == 0
{
let navigationController = viewController as? UINavigationController
navigationController?.popToRootViewControllerAnimated(true)
}
}
Download Sample
#IBAction func itemB(sender: UIButton) {
// do something
self.tabBarController?.selectedIndex = 0
}
In Swift 3.1
Add UITabBarControllerDelegate to your TabBar Class:
class YourClass: UITabBarController, UITabBarControllerDelegate {
After:
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
let yourView = self.viewControllers![self.selectedIndex] as! UINavigationController
yourView .popToRootViewControllerAnimated(false)
}
I'm new to Swift, and about a 5 out of 10 on the Objective-C knowledge scale..
I created a basic three tab Swift application. Each tab has an associated swift class file, e.g. FirstViewController.swift , SecondViewController.swift, ThirdViewController.swift.
When I select the third tab, I now open the application preference settings using a viewDidAppear function override in ThirdViewController.swift, e.g.:
override func viewDidAppear(animated: Bool) {
// open app preference s
if let url = NSURL(string:UIApplicationOpenSettingsURLString) {
UIApplication.sharedApplication().openURL(url)
}
}
Though, prior to opening the preferences, I would like to set the active tab back to the first tab. How is this done elegantly in Swift.
The following does not work:
self.tabBarController.selectedIndex = 0
As the UIViewController of the ThirdViewController class does not have a tabBarController.
Brian.
User Omkar responded above with the correct answer. I can successfully switch the first tab using the following viewDidAppear in ThirdViewController.swft
override func viewDidAppear(animated: Bool) {
self.tabBarController?.selectedIndex = 0
}
I followed #Paolo Sangregorio way The code to do it as follows
in appdelegate find applicationDidBecomeActive function and add this lines
let tabBarController = self.window?.rootViewController as! UITabBarController
tabBarController.selectedIndex = 0
//Use viewWillAppear (instead of) viewDidAppear to prevent a screen flicker
var freshLaunch = true
override func viewWillAppear(animated: Bool) {
if freshLaunch == true {
freshLaunch = false
self.tabBarController.selectedIndex = 2
}
}
If it is a tab bar application you should have the tabbarcontroller variable in your appdelegate.
You can set the current tab by setting the selectedIndex on that variable, from the appdelegate.
In case you are using Tab bar item
Swift 3
#IBOutlet weak var Tabbar: UITabBar!
override func viewDidLoad() {
.........
tabBar.selectedItem = tabBar.items![newIndex]
}
If you want him to come without delay. Yo can use in viewDidLoad()
override open func viewDidLoad() {
super.viewDidLoad()
// Set Default tab
self.selectedIndex = 1
}