Programmatically set the UITabBarItem icon for every linked ViewController - ios

Currently we are changing the image shown in the UITabBarItem like this:
override func viewDidLoad() {
super.viewDidLoad()
// register additional NIB files
tableView.registerNib(UINib(nibName: "WeekDayTableViewCell", bundle: nil), forCellReuseIdentifier: "WeekDayCell")
tabBarItem.title = "Week".localized
tabBarItem.image = UIImage.fontAwesomeIconWithName(FontAwesome.Calendar, textColor: UIColor.blueColor(), size: CGSizeMake(30, 30))
}
The problem with this is, that you have to click on every tab to load the corresponding images.
I thought I could change the images in the UITabBarViewController if I get the list of all UITabBarItems. But this list is always empty.
Which is the correct way to do this?

An alternative—perhaps a more Swift-y—way to do this is to set the icons from a Dictionary of tabs. We have an enum TabTitles for the names of the tabs, and tabIcons to look it up.
This way, there's not so much to remember to change if you add a tab or change the order.
private enum TabTitles: String, CustomStringConvertible {
case Stacks
case Settings
case Clusters
case Services
case Registries
private var description: String {
return self.rawValue
}
}
private var tabIcons = [
TabTitles.Stacks: FontAwesome.Clone,
TabTitles.Settings: FontAwesome.Gears,
TabTitles.Clusters: FontAwesome.Cubes,
TabTitles.Services: FontAwesome.Exchange,
TabTitles.Registries: FontAwesome.Institution
]
override func viewDidLoad() {
super.viewDidLoad()
if let tabBarItems = tabBar.items {
for item in tabBarItems {
if let title = item.title,
tab = TabTitles(rawValue: title),
glyph = tabIcons[tab] {
item.image = UIImage.fontAwesomeIconWithName(glyph, textColor: UIColor.blueColor(), size: CGSizeMake(30, 30))
}
}
}
}

I found out, if I add this code to the viewDidAppear in the UITabBarController it will work.
let tabBarItems = tabBar.items! as [UITabBarItem]
tabBarItems[0].title = "Week".localized
tabBarItems[0].image = UIImage.fontAwesomeIconWithName(FontAwesome.Calendar, textColor: UIColor.blueColor(), size: CGSizeMake(30, 30))
tabBarItems[1].title = "Settings".localized
tabBarItems[1].image = UIImage.fontAwesomeIconWithName(FontAwesome.Gears, textColor: UIColor.blueColor(), size: CGSizeMake(30, 30))

Swift 5
You can set tabBarItems icons from your first controller 'viewDidLoad' method.
func setupTabBar() {
tabBarController?.tabBar.items![0].title = "Week"
tabBarController?.tabBar.items![0].image = #imageLiteral(resourceName: "icons8-clinic")
tabBarController?.tabBar.items![1].title = "Profile"
tabBarController?.tabBar.items![1].image = #imageLiteral(resourceName: "icons8-phone_not_being_used_filled")
}

Use item.image = UIImage.fontAwesomeIcon(name: glyph, textColor: UIColor.black, size: CGSize(width: 30, height: 30)) for Swift 3.
Also remember to import FontAwesome_swift

Related

Progressview tintColorIssue

I have created a progressview according to number images as you can see in below code.
let view = UIView()
view.backgroundColor = .clear
let progressView = UIProgressView(frame: CGRect(x: 0, y: 0, width: frameOfParentView.width/3 - 8, height: 30))
progressView.progressViewStyle = .default
progressView.progress = 0.0
progressView.tintColor = .red
progressView.trackTintColor = .gray
progressView.layoutIfNeeded()
view.addSubview(progressView)
self.arrayOfProgrssView.append(progressView)
As you can see in gif at starting point tintColor alpha is little bit less but when it tense to reach at 100% it is fully red.
I also tried with below code:-
progressView.progressTintColor = .red
but did not get expected result.
To perform animation,
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
UIView.animate(withDuration: self.animationInMS) {
progressView.setProgress(1, animated: true)
}
}
progressView.layoutIfNeeded()
Issue in iOS 15:-
As you see below result with other colour.
Note:- I have checked in iOS 12.4 it's working properly as you can see into image.
Please let me know is anything require from my side.
Thanks in advance
This does appear to be "new behavior" where the alpha value matches the percent completion -- although, after some quick searching I haven't found any documentation on it.
One option as a work-around: set the .progressImage instead of the tint color.
So, use your favorite code to generate a solid-color image, such as:
extension UIImage {
public static func withColor(_ color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let image = UIGraphicsImageRenderer(size: size, format: format).image { rendererContext in
color.setFill()
rendererContext.fill(CGRect(origin: .zero, size: size))
}
return image
}
}
Then, instead of:
progressView.tintColor = .red
use:
let img = UIImage.withColor(.red)
progressView.progressImage = img
Not fully tested, but to avoid the need to change existing code, you might also try:
extension UIProgressView {
open override var tintColor: UIColor! {
didSet {
let img = UIImage.withColor(tintColor)
progressImage = img
}
}
}
Now you can keep your existing progressView.tintColor = .red

Can't see tab bar images

I can't see the tabbar images when i simulate my app.
I tried changing the "render as original image" solution, but it didn't work
import UIKit
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setupTabBar()
tabBar.barTintColor = UIColor(red: 0.1255, green: 0.1608, blue: 0.149, alpha: 1)
}
func setupTabBar() {
let homeController = UINavigationController(rootViewController: HomeViewController())
homeController.tabBarItem.image = UIImage(named: "homeglyph_green")
homeController.tabBarItem.selectedImage = UIImage(named: "homeglyph_lightgreen")
let statsController = UINavigationController(rootViewController: StatsViewController())
statsController.tabBarItem.image = UIImage(named: "statsglyph_green")
statsController.tabBarItem.selectedImage = UIImage(named: "statsglyph_lightgreen")
viewControllers = [homeController, statsController]
guard let items = tabBar.items else { return }
for item in items {
item.imageInsets = UIEdgeInsets(top: 4, left: 0, bottom: -4, right: 0)
}
}
}
Make sure Your Image name is same as your store name in Image assets..
if name is same then open Image assets..
1)select Your Image
2) go on Image set
3) change Render As option default to Template Image and run project..
hope fully help this solution..
image-You can find where you should change

Refresh View After Data Changes

Im trying to make real time chat, It is going good but I have a problem.
I have added a subview to navigationController instead of title. I have an avatar, full name of user and status in this view like whatsapp. I have got user is online string from pusher service and I want to display it in my subview bottom of user full name like whatsapp. How can I refresh the view when I get online string from func onPresenceChanged(stateChange method ?
I have added self.navbar.setNeedsDisplay() but it is not working.
ChatViewController.swift (I cropped it for quick review)
class ChatViewController: UIViewController, UITextFieldDelegate, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, PCRoomDelegate {
var navbarView = UIView()
var navbarAvatar = UIImageView()
var navbar = UILabel()
var statusString = [String]()
var usernameString = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.navbarView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width / 1.4, height: 44.0))
self.navbarView.backgroundColor = UIColor.clear
self.navbar = UILabel(frame: CGRect(x: 44.0, y: 0.0, width: UIScreen.main.bounds.width / 1.4 - 44, height: 44.0))
self.navbar.backgroundColor = UIColor.clear
self.navbar.numberOfLines = 2
self.navbar.textAlignment = NSTextAlignment.left
let bodyText = NSMutableAttributedString(string: "Halil İbrahim YÜCE", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .bold)])
usernameString.append("\n#" + "halilyc")
bodyText.append(NSAttributedString(string: usernameString.last!, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13), NSAttributedString.Key.foregroundColor : UIColor.gray]))
self.navbar.attributedText = bodyText
self.navbarView.addSubview(navbar)
self.navigationItem.titleView = self.navbarView
}
func onPresenceChanged(
stateChange: PCPresenceStateChange,
user: PCUser
) {
print("User \(user.displayName)'s presence changed to \(stateChange.current.rawValue)")
self.statusString.append(stateChange.current.rawValue)
self.navbar.setNeedsDisplay()
if statusString.count != 0 {
self.usernameString.append("\n#" + statusString.last!)
}
}
}
Assigning a string variable to a UILabel's attributedText property doesn't create a binding between that string and the label; you need to update attributedText if you want to see an updated value in your UI. Also, UI updates must be performed on the main thread; your update from the network will be delivered on a background thread.
Finally, I don't see any need to keep an array of statuses, you just need the most recent.
You can refactor to create a computed variable that provides the current user status text for your navigation bar.
It isn't clear how you are intending to display the display name, the username and the status (There are 3 things, but only 2 lines). I have just made it three lines and you can adapt as you require.
class ChatViewController: UIViewController, UITextFieldDelegate, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, PCRoomDelegate {
var navbarView: UIView!
var navbarAvatar: UIImageView!
var navbar: UILabel!
var status: String? {
didSet {
self.updateNavText()
}
var username: String? {
didSet {
self.updateNavText()
}
var displayName: String?
didSet {
self.updateNavText()
}
var statusText: NSAttributedString {
let text = NSMutableAttributedString(string: displayName ?? "", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .bold)])
if let username = self.username {
text.append("\n#\(username)")
}
if let status = self.status {
text.append("\n\(status)")
}
return text
}
override func viewDidLoad() {
super.viewDidLoad()
self.navbarView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width / 1.4, height: 44.0))
self.navbarView.backgroundColor = UIColor.clear
self.navbar = UILabel(frame: CGRect(x: 44.0, y: 0.0, width: UIScreen.main.bounds.width / 1.4 - 44, height: 44.0))
self.navbar.backgroundColor = UIColor.clear
self.navbar.numberOfLines = 2
self.navbar.textAlignment = NSTextAlignment.left
let bodyText = NSMutableAttributedString(string: "Halil İbrahim YÜCE", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .bold)])
self.updateNavText()
self.navbarView.addSubview(navbar)
self.navigationItem.titleView = self.navbarView
}
func onPresenceChanged(
stateChange: PCPresenceStateChange,
user: PCUser
) {
print("User \(user.displayName)'s presence changed to \(stateChange.current.rawValue)")
DispatchQueue.main.async {
self.status = stateChange.current.rawValue
}
}
func updateNavText() {
self.navbar.attributedText = self.statusText
}
You can manage things like:
First, create a function to set up your all UI objects adding on viewController's view or whatever you want to add. Call this method (like setupInitialLayout()) in viewDidLoad(). By calling this method in viewDidLoad(), because you just need to add components only once and you don't need to call this method again.
Now create another method in which write your all code like set your data whatever you want to display. Call this method (like setupData()) in viewWillAppear(). Also, you can call this method setupData() whenever you need like just user is online you are chatting with and you want to show as Online, just call this method only.

iOS swift: add kern space in the tab bar text

I'm not able to add kern space into the tab bar attributed text.
The UITabBar in question is a custom tabBar, you can find the code below.
I'm using the "attributed key" dictionary to add attributes to the items title, but I'm having an issue with the kern space.
class ProfileTabBar: UITabBar {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setStyle()
}
required override init(frame: CGRect) {
super.init(frame: frame)
self.setStyle()
}
func setStyle() {
self.tintColor = Style.shared.primary1
// Disable the default border
self.layer.borderWidth = 0.0
self.clipsToBounds = true
// Create a new bottom border
let bottomLine = CALayer()
let screenWidth = UIScreen.main.bounds.width
//let viewForFrame = self.superview ?? self
//let screenWidth = viewForFrame.bounds.width
bottomLine.frame = CGRect(x: 0.0, y: self.frame.height - 1, width: screenWidth, height: 2.0)
bottomLine.backgroundColor = UIColor(red: 235.0/255, green: 235.0/255, blue: 235.0/255, alpha: 1.0).cgColor
self.layer.addSublayer(bottomLine)
// Get the size of a single item
let markerSize = CGSize(width: screenWidth/CGFloat(self.items!.count), height: self.frame.height)
// Create the selection indicator
self.selectionIndicatorImage = UIImage().createSelectionIndicator(color: self.tintColor, size: markerSize , lineWidth: 3.0)
// Customizing the items
if let items = self.items {
for item in items {
item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -15)
let attributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font: UIFont(name: Style.shared.fontBold.fontName, size: 14) as Any,
NSAttributedStringKey.kern: NSNumber(value: 1.0)
]
item.setTitleTextAttributes(attributes, for: .normal)
}
}
}
All the attributes works except for the kern. What I'm doing wrong?
This question is old and there is an even older answer here. It appears that UITabBarItem appearance ignores NSAttributedString.Key.kern. That leaves us with a few options.
Subclass UITabBarItem this isn't easy because UITabBarItem inherits from UIBarItem which is an NSObject not a UIView.
Subclass UITabBar this can be done, but involves a decent amount of work for just some kern. You'll have to use UIButton instead of UITabBarItem so that the kern is applied.
You can add spacing using unicode characters in your title. This is really easy and can probably achieve the spacing you're looking for with just a few lines of code.
Unicode spacing:
U+0020 1/4 em
U+2004 1/3 em
U+2005 1/4 em
U+2006 1/6 em
U+2008 The width of a period “.”
U+2009 1/5 em (or sometimes 1/6 em)
You can use a unicode character in a String in Swift like this "\u{2006}". That means we can insert a small space between all the characters in our tabBarItem title. Like this:
extension String {
var withOneSixthEmSpacing: String {
let letters = Array(self)
return letters.map { String($0) + "\u{2006}" }.joined()
}
Using this for our tabBarItems:
self.navigationController.tabBarItem = UITabBarItem(
title: "Home".withOneSixthEmSpacing,
image: homeImage,
selectedImage: homeSelectedImage
)
Visually we end up with:
Instead of:
Another workaround is to subclass UITabBarController, and set the kerning in viewDidLayoutSubviews.
class FooTabBarController: UITabBarController {
private var tabBarButtons: [UIControl] {
tabBar.subviews.compactMap { $0 as? UIControl }
}
private var tabBarButtonLabels: [UILabel] {
tabBarButtons.compactMap { $0.subviews.first { $0 is UILabel } as? UILabel }
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tabBarButtonLabels.forEach {
if let attributedText = $0.attributedText {
let mutable = NSMutableAttributedString(attributedString: attributedText)
mutable.addAttribute(.kern, value: 0.5, range: .init(location: 0, length: mutable.length))
$0.attributedText = mutable
$0.sizeToFit()
}
}
}
}
The caveats to this solution are:
It is somewhat fragile. It can break if Apple changes the view structure in the tab bar, ie if they stop using UIControl, or if they change the subview heirarchy.
It isn't all that efficient because the kerning has to be set every layout cycle.
I loved #DoesData's answer, it really helped me out a lot.
Here's a more "swifty" version of it I came up with if it helps anyone:
extension String {
var withAddedSpacing: String {
Array(self)
.compactMap { String($0) }
.joined(separator: "\u{2006}")
}
}

Remove tab bar item text, show only image

Simple question, how can I remove the tab bar item text and show only the image?
I want the bar items to like in the instagram app:
In the inspector in xcode 6 I remove the title and choose a #2x (50px) and a #3x (75px) image. However the image does not use the free space of the removed text. Any ideas how to achieve the same tab bar item image like in the instagram app?
You should play with imageInsets property of UITabBarItem. Here is sample code:
let tabBarItem = UITabBarItem(title: nil, image: UIImage(named: "more")
tabBarItem.imageInsets = UIEdgeInsets(top: 9, left: 0, bottom: -9, right: 0)
Values inside UIEdgeInsets depend on your image size. Here is the result of that code in my app:
// Remove the titles and adjust the inset to account for missing title
for(UITabBarItem * tabBarItem in self.tabBar.items){
tabBarItem.title = #"";
tabBarItem.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0);
}
Here is how you do it in a storyboard.
Clear the title text, and set the image inset like the screenshot below
Remember the icon size should follow the apple design guideline
This means you should have 25px x 25px for #1x, 50px x 50px for #2x, 75px x 75px for #3x
Using approach with setting each UITabBarItems title property to ""
and update imageInsets won't work properly if in view controller self.title is set. For example if self.viewControllers of UITabBarController are embedded in UINavigationController and you need title to be displayed on navigation bar. In this case set UINavigationItems title directly using self.navigationItem.title, not self.title.
If you're using storyboards this would be you best option. It loops through all of the tab bar items and for each one it sets the title to nothing and makes the image full screen. (You must have added an image in the storyboard)
for tabBarItem in tabBar.items!
{
tabBarItem.title = ""
tabBarItem.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0)
}
Swift version of ddiego answer
Compatible with iOS 11
Call this function in viewDidLoad of every first child of the viewControllers after setting title of the viewController
Best Practice:
Alternativelly as #daspianist suggested in comments
Make a subclass of like this class BaseTabBarController:
UITabBarController, UITabBarControllerDelegate and put this function
in the subclass's viewDidLoad
func removeTabbarItemsText() {
var offset: CGFloat = 6.0
if #available(iOS 11.0, *), traitCollection.horizontalSizeClass == .regular {
offset = 0.0
}
if let items = tabBar.items {
for item in items {
item.title = ""
item.imageInsets = UIEdgeInsets(top: offset, left: 0, bottom: -offset, right: 0)
}
}
}
iOS 11 throws a kink in many of these solutions, so I just fixed my issues on iOS 11 by subclassing UITabBar and overriding layoutSubviews.
class MainTabBar: UITabBar {
override func layoutSubviews() {
super.layoutSubviews()
// iOS 11: puts the titles to the right of image for horizontal size class regular. Only want offset when compact.
// iOS 9 & 10: always puts titles under the image. Always want offset.
var verticalOffset: CGFloat = 6.0
if #available(iOS 11.0, *), traitCollection.horizontalSizeClass == .regular {
verticalOffset = 0.0
}
let imageInset = UIEdgeInsets(
top: verticalOffset,
left: 0.0,
bottom: -verticalOffset,
right: 0.0
)
for tabBarItem in items ?? [] {
tabBarItem.title = ""
tabBarItem.imageInsets = imageInset
}
}
}
I used the following code in my BaseTabBarController's viewDidLoad.
Note that in my example, I have 5 tabs, and selected image will always be base_image + "_selected".
// Get tab bar and set base styles
let tabBar = self.tabBar;
tabBar.backgroundColor = UIColor.whiteColor()
// Without this, images can extend off top of tab bar
tabBar.clipsToBounds = true
// For each tab item..
let tabBarItems = tabBar.items?.count ?? 0
for i in 0 ..< tabBarItems {
let tabBarItem = tabBar.items?[i] as UITabBarItem
// Adjust tab images (Like mstysf says, these values will vary)
tabBarItem.imageInsets = UIEdgeInsetsMake(5, 0, -6, 0);
// Let's find and set the icon's default and selected states
// (use your own image names here)
var imageName = ""
switch (i) {
case 0: imageName = "tab_item_feature_1"
case 1: imageName = "tab_item_feature_2"
case 2: imageName = "tab_item_feature_3"
case 3: imageName = "tab_item_feature_4"
case 4: imageName = "tab_item_feature_5"
default: break
}
tabBarItem.image = UIImage(named:imageName)!.imageWithRenderingMode(.AlwaysOriginal)
tabBarItem.selectedImage = UIImage(named:imageName + "_selected")!.imageWithRenderingMode(.AlwaysOriginal)
}
Swift 4 approach
I was able to do the trick by implementing a function that takes a TabBarItem and does some formatting to it.
Moves the image a little down to make it be more centered and also hides the text of the Tab Bar.
Worked better than just setting its title to an empty string, because when you have a NavigationBar as well, the TabBar regains the title of the viewController when selected
func formatTabBarItem(tabBarItem: UITabBarItem){
tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
tabBarItem.setTitleTextAttributes([NSAttributedStringKey.foregroundColor:UIColor.clear], for: .selected)
tabBarItem.setTitleTextAttributes([NSAttributedStringKey.foregroundColor:UIColor.clear], for: .normal)
}
Latest syntax
extension UITabBarItem {
func setImageOnly(){
imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
setTitleTextAttributes([NSAttributedString.Key.foregroundColor:UIColor.clear], for: .selected)
setTitleTextAttributes([NSAttributedString.Key.foregroundColor:UIColor.clear], for: .normal)
}
}
And just use it in your tabBar as:
tabBarItem.setImageOnly()
Here is a better, more foolproof way to do this other than the top answer:
[[UITabBarItem appearance] setTitleTextAttributes:#{NSForegroundColorAttributeName: [UIColor clearColor]}
forState:UIControlStateNormal];
[[UITabBarItem appearance] setTitleTextAttributes:#{NSForegroundColorAttributeName: [UIColor clearColor]}
forState:UIControlStateHighlighted];
Put this in your AppDelegate.didFinishLaunchingWithOptions so that it affects all tab bar buttons throughout the life of your app.
A minimal, safe UITabBarController extension in Swift (based on #korgx9 answer):
extension UITabBarController {
func removeTabbarItemsText() {
tabBar.items?.forEach {
$0.title = ""
$0.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
}
Based on the answer of ddiego, in Swift 4.2:
extension UITabBarController {
func cleanTitles() {
guard let items = self.tabBar.items else {
return
}
for item in items {
item.title = ""
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
}
And you just need to call self.tabBarController?.cleanTitles() in your view controller.
Custom TabBar - iOS 13, Swift 5, XCode 11
TabBar items without text
TabBar items centered vertically
Rounded TabBar view
TabBar Dynamic position and frames
Storyboard based. It can be achieved easily programmatically too. Only 4 Steps to follow:
Tab Bar Icons must be in 3 sizes, in black color. Usually, I download from fa2png.io - sizes: 25x25, 50x50, 75x75. PDF image files do not work!
In Storyboard for the tab bar item set the icon you want to use through Attributes Inspector. (see screenshot)
Custom TabBarController -> New File -> Type: UITabBarController -> Set on storyboard. (see screenshot)
UITabBarController class
class RoundedTabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Custom tab bar view
customizeTabBarView()
}
private func customizeTabBarView() {
let tabBarHeight = tabBar.frame.size.height
self.tabBar.layer.masksToBounds = true
self.tabBar.isTranslucent = true
self.tabBar.barStyle = .default
self.tabBar.layer.cornerRadius = tabBarHeight/2
self.tabBar.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let viewWidth = self.view.bounds.width
let leadingTrailingSpace = viewWidth * 0.05
tabBar.frame = CGRect(x: leadingTrailingSpace, y: 200, width: viewWidth - (2 * leadingTrailingSpace), height: 49)
}
}
Result
code:
private func removeText() {
if let items = yourTabBarVC?.tabBar.items {
for item in items {
item.title = ""
}
}
}
In my case, same ViewController was used in TabBar and other navigation flow. Inside my ViewController, I have set self.title = "Some Title" which was appearing in TabBar regardless of setting title nil or blank while adding it in tab bar. I have also set imageInsets as follow:
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
So inside my ViewController, I have handled navigation title as follow:
if isFromTabBar {
// Title for NavigationBar when ViewController is added in TabBar
// NOTE: Do not set self.title = "Some Title" here as it will set title of tabBarItem
self.navigationItem.title = "Some Title"
} else {
// Title for NavigationBar when ViewController is opened from navigation flow
self.title = "Some Title"
}
Based on all the great answers on this page, I've crafted another solution that also allows you to show the the title again. Instead of removing the content of title, I just change the font color to transparent.
extension UITabBarItem {
func setTitleColorFor(normalState: UIColor, selectedState: UIColor) {
self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: normalState], for: .normal)
self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: selectedState], for: .selected)
}
}
extension UITabBarController {
func hideItemsTitle() {
guard let items = self.tabBar.items else {
return
}
for item in items {
item.setTitleColorFor(normalState: UIColor(white: 0, alpha: 0), selectedState: UIColor(white: 0, alpha: 0))
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
func showItemsTitle() {
guard let items = self.tabBar.items else {
return
}
for item in items {
item.setTitleColorFor(normalState: .black, selectedState: .yellow)
item.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
}
Easiest way and always works:
class TabBar: UITabBar {
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { subview in
if subview is UIControl {
subview.subviews.forEach {
if $0 is UILabel {
$0.isHidden = true
subview.frame.origin.y = $0.frame.height / 2.0
}
}
}
}
}
}
make a subclass of UITabBarController and assign that to your tabBar , then in the viewDidLoad method place this line of code:
tabBar.items?.forEach({ (item) in
item.imageInsets = UIEdgeInsets.init(top: 8, left: 0, bottom: -8, right: 0)
})
If you are looking to center the tabs / change the image insets without using magic numbers, the following has worked for me (in Swift 5.2.2):
In a UITabBarController subclass, you can add add the image insets after setting the view controllers.
override var viewControllers: [UIViewController]? {
didSet {
addImageInsets()
}
}
func addImageInsets() {
let tabBarHeight = tabBar.frame.height
for item in tabBar.items ?? [] where item.image != nil {
let imageHeight = item.image?.size.height ?? 0
let inset = CGFloat(Int((tabBarHeight - imageHeight) / 4))
item.imageInsets = UIEdgeInsets(top: inset,
left: 0,
bottom: -inset,
right: 0)
}
}
Several of the options above list solutions for dealing with hiding the text.

Resources