Is it possible to hide or disable a tab bar item on a tab bar throughout the entire app for a certain use case?
Example:
While the user is logged in, and they do not have a Role of 'manager', the last tab bar item will be hidden throughout the app. When they log in again as a manager the last tab bar will be enabled and not hidden.
If you are inside the source file of the UITabBarController, just add below code in viewDidLoad method to disable last item
Also below code assumes you are having UITabBarItem items in tab bar. Otherwise you know what type of item is it so you can cast accordingly
if let items = tabBar.items as? [UITabBarItem] {
if items.count > 0 {
let itemToDisable = items[items.count - 1]
itemToDisable.enabled = false
}
}
Better code (in Swift 4):
tabBar.items?.forEach { $0.isEnabled = false }
Better solution (in Swift 5)
tabBarControlled?.tabBar.items?[2].isEnabled = isManager
Swift 5 one liner
let n = 2. //The tab number to disable
self.tabBarController!.tabBar.items![n].isEnabled = false
Related
My VC starts with stackView attached with Align Bottom to Safe Area .
I have tabBar, but in the beginning is hidden tabBar.isHidden = true.
Later when the tabBar appears, it hides the stackView
So I need function that refresh constraints after tabBar.isHidden = false
When I start the app with tabBar.isHidden = false the stackView is shown properly.
Tried with every function like: stackView.needsUpdateConstraints() , updateConstraints() , setNeedsUpdateConstraints() without success.
Now I'm changing the bottom programatically, but when I switch the tabBarIndex and return to that one with changed bottom constraints it detects the tabBar and lifts the stackView under another view (which is not attached with constraints). Like is refreshing again the constraints. I'm hiding and showing this stackView with constrains on/off screen.
I need to refresh constraints after tabBar.isHidden = false, but the constraints don't detect the appearance of the tabBar.
As I mention switching between tabBars fixes the issue, so some code executes to detecting tabBar after the switch. Is anyone know this code? I tried with calling the methods viewDidLayoutSubviews and viewWillLayoutSubviews without success... Any suggestions?
This amateur approach fixed my bug... :D
tabBarController!.selectedIndex = 1
tabBarController!.selectedIndex = 0
Or with an extension
extension UITabBarController {
// Basically just toggles the tabs to fix layout issues
func forceConstraintRefresh() {
// Get the indices we need
let prevIndex = selectedIndex
var newIndex = 0
// Find an unused index
let items = viewControllers ?? []
find: for i in 0..<items.count {
if (i != prevIndex) {
newIndex = i
break find
}
}
// Toggle the tabs
selectedIndex = newIndex
selectedIndex = prevIndex
}
}
Usage (called when switching dark / light mode):
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
tabBarController?.forceConstraintRefresh()
}
If you want to update view's layout, you can try layoutIfNeeded() function.
after updating stackView constraints call this method:
stackView.superview?.layoutIfNeeded()
Apple's Human Interface Guidelines indicate that one should not mess around with the Tab Bar, which is why (I'm guessing) setting tabBar.isHidden doesn't properly update the rest of the view hierarchy.
Quick searching comes up with various UITabBarController extensions for showing / hiding the tab bar... but they all appear to push the tabBar down off-screen, rather than setting its .isHidden property. May or may not be suitable for your use.
I'm assuming from your comments that your VC in tab index 0 has a button (or some other action) to show / hide the tabBar?
If so, here is an approach that may do the job....
Add this enum in your project:
enum TabBarState {
case toggle, show, hide
}
and put this func in that view controller:
func showOrHideTabBar(state: TabBarState? = .toggle) {
if let tbc = self.tabBarController {
let b: Bool = (state == .toggle) ? !tbc.tabBar.isHidden : state == .hide
guard b != tbc.tabBar.isHidden else {
return
}
tbc.tabBar.isHidden = b
view.frame.size.height -= 0.1
view.setNeedsLayout()
view.frame.size.height += 0.1
}
}
You can call it with:
// default: toggles isHidden
showOrHideTabBar()
// toggles isHidden
showOrHideTabBar(state: .toggle)
// SHOW tabBar (if it's hidden)
showOrHideTabBar(state: .show)
// HIDE tabBar (if it's showing)
showOrHideTabBar(state: .hide)
I would expect that simply pairing .setNeedsLayout() with .layoutIfNeeded() after setting the tabBar's .isHidden property should do the job, but apparently not.
The quick frame height change (combined with .setNeedsLayout()) does trigger auto-layout, though, and the height change is not visible.
NOTE: This is the result of very brief testing, on one device and one iOS version. I expect it will work across devices and versions, but I have not done complete testing.
My App is all based on API data. I am able to create a tab bar controller from api data but just ended up in confusion on how to create view controllers for each tab bar item dynamically. Here is the code so far -
for i in 0..<navgTitle.count {
print(navgTitle.count)
controller1 = WeatherViewController()
controller1.title = navgTitle[i]
controller1.tabBarItem.title = navgTitle[i]
controller1.tabBarItem.image = UIImage(named: "AppIcon")?.withRenderingMode(.alwaysOriginal)
array1.append(controller1)
}
print(array1)
tt.viewControllers = array1
self.view.addSubview(tt.view)
self.tabBarControllr.moreNavigationController.show(tt, sender: self)
tt.customizableViewControllers = []
}
In the above code, in the 'for' loop i want to create view controllers for each tab bar item (say 'HomeViewController', WeatherViewController' etc). I do not know how to do that and is it really possible? I can not create view controller classes manually because each app has their own tab bar items to show. Please suggest a way to do this..Thanks in Advance
While clicking on the button , i am moving to another view controller using the following code.
var window: UIWindow?
window = UIWindow.init(frame: UIScreen.main.bounds)
window?.autoresizesSubviews = true
window?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let trackingViewController = LoginCameraViewController.init(screen:
.main)
window?.rootViewController = trackingViewController
window?.addSubview((trackingViewController?.view)!)
window?.makeKeyAndVisible()
window?.layoutSubviews()
For every button click, a new window is added to the application.I want to remove the latest window added.
The number of windows present in the application can be known by using following code.
let windowz = UIApplication.shared.windows
print("subviews",windowz)
I think you get the wrong concept of navigation in iOS. Window is like a root object in which ViewControllers appear. So probably the solution you're looking in a first place is UINavigationController.
Apple Documentation on Navigation
For iOS 13 i was able to do it this way
I created array which contains the window using which this new viewController is being presented,
var arrWindow = [UIWindow]()
arrWindow.append(yourNewWindow)
// Note: This will be stored as strong reference so need to remove it.
Also store your original window in variable
let originalWindow = yourOriginalWindow
// Note: Same goes for this as well ,this will be stored as strong reference so need to remove it.
At the time of removing there are many ways to do it but this was the most suited way for me,
func removeAppendedWindow() {
for window in arrWindow {
if window != originalWindow {
if let index = arrWindow.index(of: window) {
window.isHidden = true
arrWindow.remove(at: index)
}
}
}
}
In the below code windowz is normal array.
let windowz = UIApplication.shared.windows
You can remove last by using
windowz.removeLast()
You should use View Controller instead of adding windows and pop it instead where you are removing the window.
Window is only one object for app and will contain the views.
Please correct your understanding and use View controllers.
my application has three tabs. In tab 1 a view controller lets the user add the name of a contact to tab 2 (A button does this task). As soon as the button is pressed the name gets added to tab 2 and the button is disabled for future operations.
But when the user deletes the name from tab 2 the button should be enabled again. I am able to do this if i reload the tab 1 through the navigation controller but if i switch to tab 1 from 2 the button is still disabled even after the user has deleted the name from tab 2.
how to refresh tab 1 when the user has deleted the name in tab 2
Access Your tab 1 VC from tab 2 VC by TabbarController. and enable the button after deleting the data .
if let tabbarControllers = self.tabBarController?.childViewControllers {
if let firstController = tabbarControllers[0] as? YourFirstVC {
firstController.yourButtonOutlet.isUserInteractionEnabled = true
}
}
And if your tab 1 vc embeded in a navigationcontroller then use.
if let tabbarControllers = self.tabBarController?.childViewControllers {
if let firstControllersNavigation = tabbarControllers[0] as? UINavigationController {
if let firstController = firstControllersNavigation.childViewControllers[0] as? YourFirstVC {
firstController.yourButtonOutlet.isUserInteractionEnabled = true
}
}
}
I am trying to create a new OS X application. I've got a table view where information should be displayed, underneath it there is a Segmented Control section with two buttons (+ and -).
I used this code to show up a dialog / modal:
#IBAction func addDevices(_ sender: NSSegmentedControl) {
let index = sender.selectedSegment
let controller = (self.view.window?.windowController as! MainWindowController)
controller.createNewDevices(sender)
print("works")
}
How can I differentiate between + button and - button pressed? At the moment, both buttons call up my dialog.
Thanks for your attention, I hope someone can help me!
P.S.: New to Stack, if I didn't post as you expected it, please let me know what I can do better!
You can use selectedSegment property.
var selectedSegment: Int
The index of the selected segment of the control, or -1 if no segment is selected.
Button "+" have index 0, and "-" have index 1.
You can do that:
switch sender.selectedSegment {
case 0: // do actions if "+"
case 1: // do actions if "-"
default: print("No actions for this button")
}