UITabBar items title position issue - ios

I cannot solve this issue, related to the position of the title of every single tab bar item in a UITabBar. I tried with this:
extension UITabBar {
override open var traitCollection: UITraitCollection {
if UIDevice.current.userInterfaceIdiom == .pad {
return UITraitCollection(horizontalSizeClass: .compact)
}
return super.traitCollection
}
}
With no success. How can I solve this misalignment?

Check if this is worked, change values in UIOffset per what position you want.
if #available(iOS 13, *) {
let appearance = UITabBarAppearance()
// set padding between tabbar item title and image
appearance.stackedLayoutAppearance.selected.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 4)
appearance.stackedLayoutAppearance.normal.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 4)
self.tabBar.standardAppearance = appearance
} else {
// set padding between tabbar item title and image
UITabBarItem.appearance().titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 4)
}

Related

How to correct Tab Bar height issue on iPhone X

I'm having an issue with my app when testing for iPhone X. I'm not sure how to adjust this issue, as well as not make it an issue for non iPhone X sizes. This only seems to be an issue on the iPhone X simulator.
On iOS 12.1 I've solved this issue by overriding safeAreaInsets in the UITabBar subclass:
class TabBar: UITabBar {
private var cachedSafeAreaInsets = UIEdgeInsets.zero
override var safeAreaInsets: UIEdgeInsets {
let insets = super.safeAreaInsets
if insets.bottom < bounds.height {
cachedSafeAreaInsets = insets
}
return cachedSafeAreaInsets
}
}
For iOS 13.0 onward,
class TabBar: UITabBar {
private var cachedSafeAreaInsets = UIEdgeInsets.zero
let keyWindow = UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }
.first?.windows
.filter { $0.isKeyWindow }
.first
override var safeAreaInsets: UIEdgeInsets {
if let insets = keyWindow?.safeAreaInsets {
if insets.bottom < bounds.height {
cachedSafeAreaInsets = insets
}
}
return cachedSafeAreaInsets
}
}
Create a separate file with the following code:
extension UITabBar {
override open func sizeThatFits(_ size: CGSize) -> CGSize {
super.sizeThatFits(size)
guard let window = UIApplication.shared.keyWindow else {
return super.sizeThatFits(size)
}
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = window.safeAreaInsets.bottom + 40
return sizeThatFits
}
}
"File inspector" from right of Xcode storyboard, enable Safe Area guide layout to support your app in iPhone
This post describes it really well.
For iOS 11.3 this worked for me:
func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tabBar.invalidateIntrinsicContentSize()
}
In Constraints -
If you are giving bottom space with "Bottom Layout Guide", then this issue will occur.
Solution:
Give bottom space with respect to superview. This will work 100% perfect.
This worked for me.
[self.tabBar.bottomAnchor constraintEqualToAnchor:self.view.layoutMarginsGuide.bottomAnchor].active = YES;
I had a a similar issue. I was setting the selectionIndicatorImage in viewDidLoad(). Moving the code to viewDidLayoutSubviews() fixed my issue.
Just align the bottom of the UITabBar to the superview, not to the safe area. If you align it to safe area it will be like this:
And when aligned to the superview, it will show correctly:
I think this is because Apple gave the tab bar items a default margin to the bottom if it is on iPhone X as they want the tab bar to be extended to the bottom of the screen to avoid a floating bar.
Follow below guidelines for setting the UITabbar selectionIndicatorImage.
UITabBar.appearance().selectionIndicatorImage = #YOUR_IMAGE
Make sure your image height is 48.
The default height of tabbar selectionIndicatorImage is 49, But in iPhone X set image height equals to 48.
The solution for me was that I had a custom UITabBar height set, something like this:
override func viewWillLayoutSubviews() {
var tabFrame = tabBar.frame
tabFrame.size.height = 60
tabFrame.origin.y = self.view.frame.size.height - 60
tabBar.frame = tabFrame
}
Remove it and the tab bar will display correctly on iPhone X.
Add this code in viewDidLoad
DispatchQueue.main.async {
let size = CGSize(width: self.tabBar.frame.width / numberOfTabsFloat,
height: self.tabBar.frame.height)
let image = UIImage.drawTabBarIndicator(color: UIColor.white,
size: size,
onTop: false)
UITabBar.appearance().selectionIndicatorImage = image
self.tabBar.selectionIndicatorImage = image
}
and add this extension
extension UIImage{
//Draws the top indicator by making image with filling color
class func drawTabBarIndicator(color: UIColor, size: CGSize, onTop: Bool) -> UIImage {
let indicatorHeight = size.height
let yPosition = onTop ? 0 : (size.height - indicatorHeight)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(CGRect(x: 0, y: yPosition, width: size.width, height: indicatorHeight))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
Put this into your UITabBarViewController to correct the TabBar height if your UIViewController is rotatable.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
tabBar.sizeToFit()
}
invalidateIntrinsicContentSize of UITabBar in viewWillLayoutSubviews that may help you.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.tabBar.invalidateIntrinsicContentSize()
}
There is UITabBar subclass that solves all my issues with iPhone X iOS 11 / iOS 12
class TabBar: UITabBar {
private var _safeAreaInsets = UIEdgeInsets.zero
private var _subviewsFrames: [CGRect] = []
#available(iOS 11.0, *)
override func safeAreaInsetsDidChange() {
super.safeAreaInsetsDidChange()
if _safeAreaInsets != safeAreaInsets {
_safeAreaInsets = safeAreaInsets
invalidateIntrinsicContentSize()
superview?.setNeedsLayout()
superview?.layoutSubviews()
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
if #available(iOS 12.0, *) {
let bottomInset = safeAreaInsets.bottom
if bottomInset > 0 && size.height < 50 && (size.height + bottomInset < 90) {
size.height += bottomInset
}
}
return size
}
override var frame: CGRect {
get {
return super.frame
}
set {
var tmp = newValue
if let superview = superview, tmp.maxY !=
superview.frame.height {
tmp.origin.y = superview.frame.height - tmp.height
}
super.frame = tmp
}
}
override func layoutSubviews() {
super.layoutSubviews()
let state = subviews.map { $0.frame }
if (state.first { $0.width == 0 } == nil) {
_subviewsFrames = state
} else {
zip(subviews, _subviewsFrames).forEach { (view, rect) in
view.frame = rect
}
}
}
}
Apple has now fixed this issue in iOS 12.1.1
It's look crazy but I just need to remove this line in my code
self.view.layoutIfNeeded()
I just guess that call layoutIfNeeded on a view that doesn't appear in screen will make this problem happen.
Anyway, solution from #mohamed-ali also work correctly. Thanks you so much.
This worked for me as I am using a selection image.
tabBar.selectionIndicatorImage = UIImage.imageWithColor(color: UIColor.NewDesignColor.yellow, size: tabBarItemSize).resizableImage(withCapInsets: UIEdgeInsets.init(top: 0, left: 0, bottom: 20, right: 0))
Adding a bottom inset helps in my case.
Hope this works for your as well.
Thanks.
Although my answer is late, But let me ensure you, if you are facing any issue like this on iphone x, xs or max screen, Make sure the image size you are uploading as selection must have height = width * 48pxHeight.
After trying a few solutions, what worked for me was adding the following line to viewDidLoad:
[self.tabBar.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
Jonah's and Mehul Thakkar's answers pointed me in the right direction.
Note: I only had a blank tab bar controller in storyboard. The tab bar images and view controllers were setup using the tabBarItem properties (e.g., tabBarItem.title on the view controllers).
I was facing this cosmetic problem above iOS 11.0 and below 12.0 only.
I was having a CustomTabBar class inherited from UITabBar.
I override the frame method like below:
- (CGRect)frame{
return self.bounds;
}
It resolved this issue in most of the iOS version 11.0 *

Change navigation bar bottom border color Swift

It works with
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
self.navigationController?.navigationBar.shadowImage = UIColor.redColor().as1ptImage()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension UIColor {
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSizeMake(1, 1))
let ctx = UIGraphicsGetCurrentContext()
self.setFill()
CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
But when I add a UITableView it doesn't appear on it and when I add a UISearchView it appears but removes the navigation bar.
Anyone knows how to solve this?
You have to adjust the shadowImage property of the navigation bar.
Try this one. I created a category on UIColor as an helper, but you can refactor the way you prefer.
extension UIColor {
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSizeMake(1, 1))
let ctx = UIGraphicsGetCurrentContext()
self.setFill()
CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Option 1: on a single navigation bar
And then in your view controller (change the UIColor to what you like):
// We can use a 1px image with the color we want for the shadow image
self.navigationController?.navigationBar.shadowImage = UIColor.redColor().as1ptImage()
// We need to replace the navigation bar's background image as well
// in order to make the shadowImage appear. We use the same 1px color tecnique
self.navigationController?.navigationBar.setBackgroundImage(UIColor.yellowColor‌​().as1ptImage(), forBarMetrics: .Default)
Option 2: using appearance proxy, on all navigation bars
Instead of setting the background image and shadow image on each navigation bar, it is possible to rely on UIAppearance proxy. You could try to add those lines to your AppDelegate, instead of adding the previous ones in the viewDidLoad.
// We can use a 1px image with the color we want for the shadow image
UINavigationBar.appearance().shadowImage = UIColor.redColor().as1ptImage()
// We need to replace the navigation bar's background image as well
// in order to make the shadowImage appear. We use the same 1px color technique
UINavigationBar.appearance().setBackgroundImage(UIColor.yellowColor().as1ptImage(), forBarMetrics: .Default)
Wonderful contributions from #TheoF, #Alessandro and #Pavel.
Here is what I did for...
Swift 4
extension UIColor {
/// Converts this `UIColor` instance to a 1x1 `UIImage` instance and returns it.
///
/// - Returns: `self` as a 1x1 `UIImage`.
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
setFill()
UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
UIGraphicsEndImageContext()
return image
}
}
Using it in viewDidLoad():
/* In this example, I have a ViewController embedded in a NavigationController in IB. */
// Remove the background color.
navigationController?.navigationBar.setBackgroundImage(UIColor.clear.as1ptImage(), for: .default)
// Set the shadow color.
navigationController?.navigationBar.shadowImage = UIColor.gray.as1ptImage()
Putting #alessandro-orru's answer in one extension
extension UINavigationController {
func setNavigationBarBorderColor(_ color:UIColor) {
self.navigationBar.shadowImage = color.as1ptImage()
}
}
extension UIColor {
/// Converts this `UIColor` instance to a 1x1 `UIImage` instance and returns it.
///
/// - Returns: `self` as a 1x1 `UIImage`.
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
setFill()
UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
UIGraphicsEndImageContext()
return image
}
}
then in your view controller just add:
self.navigationController?.setNavigationBarBorderColor(UIColor.red)
From iOS 13 on, you can use the UINavigationBarAppearance() class with the shadowColor property:
if #available(iOS 13.0, *) {
let style = UINavigationBarAppearance()
style.shadowColor = UIColor.clear // Effectively removes the border
navigationController?.navigationBar.standardAppearance = style
// Optional info for follow-ups:
// The above will override other navigation bar properties so you may have to assign them here, for example:
//style.buttonAppearance.normal.titleTextAttributes = [.font: UIFont(name: "YourFontName", size: 17)!]
//style.backgroundColor = UIColor.orange
//style.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: UIFont(name: "AnotherFontName", size: 20.0)!]
} else {
// Fallback on earlier versions
}
Solution for Swift 4.0 - 5.2
Here is small extension for changing both Height and Color of bottom navbar line
extension UINavigationController
{
func addCustomBottomLine(color:UIColor,height:Double)
{
//Hiding Default Line and Shadow
navigationBar.setValue(true, forKey: "hidesShadow")
//Creating New line
let lineView = UIView(frame: CGRect(x: 0, y: 0, width:0, height: height))
lineView.backgroundColor = color
navigationBar.addSubview(lineView)
lineView.translatesAutoresizingMaskIntoConstraints = false
lineView.widthAnchor.constraint(equalTo: navigationBar.widthAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: CGFloat(height)).isActive = true
lineView.centerXAnchor.constraint(equalTo: navigationBar.centerXAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
}
}
And after adding this extension, you can call this method on any UINavigationController (e.g. from ViewController viewDidLoad())
self.navigationController?.addCustomBottomLine(color: UIColor.black, height: 20)
For iOS 13 and later
guard let navigationBar = navigationController?.navigationBar else { return }
navigationBar.isTranslucent = true
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundImage = UIImage()
appearance.backgroundColor = .clear
navigationBar.standardAppearance = appearance
} else {
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
}
for Swift 3.0 just change this line:
CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
to this:
ctx?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
There's a much better option available these days:
UINavigationBar.appearance().shadowImage = UIImage()

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.

iOS 7: Custom Back Indicator Image Position

I'm having trouble setting properly a custom back indicator image. The indicator is not centered!
Here is a pic:
I'm setting the indicator image in didFinishLaunchingWithOptions: method...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIImage *image = [UIImage imageNamed:#"Back"];
[UINavigationBar appearance].backIndicatorImage = image;
[UINavigationBar appearance].backIndicatorTransitionMaskImage = image;
return YES;
}
How can I center it?
p.s I've already read this Custom back indicator image in iOS 7 not vertically centered, but actually it didn't work for me.
UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 2, 0);
UIImage *backArrowImage = [[UIImage imageNamed:#"Back"] imageWithAlignmentRectInsets:insets];
[[UINavigationBar appearance] setBackIndicatorImage:backArrowImage];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:backArrowImage];
That happens because you are just changing the image source of the Back Indicator in your UINavigationView, and not the frame as well.
See, when the UINavigationView is created, the Back Indicator's frame is set to hold the size of the default iOS 7 back button image. The default back button image is bigger than yours, and that's why it looks not aligned.
To fix that you have to reset the Back Indicator's Frame to hold the size of your image. Another option is to create a UIButton with the right frame size and image and assign to a UIBarButtonItem. Then you can replace the backBarButtonItem from your UINavigationItem with the new UIBarButtonItem you created.
This is how I dealt with the problem using Appearance API and is working great.
When changing backButtonBackgroundImage image is automatically stretched across barButtonItem so we must resize it back to original using resizableImageWithCapInsets:.
To position it inside barButtonItem we then use imageWithAlignmentRectInsets to add caps around it. Then just assign it using setBackButtonBackgroundImage:forState:barMetrics.
Just play with the numbers and you will find the right position.
int imageSize = 24;
UIImage *barBackBtnImg = [[[UIImage imageNamed:#"backButton"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, imageSize, 0, 0)] imageWithAlignmentRectInsets:UIEdgeInsetsMake(0, -10, 0, -10)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:barBackBtnImg forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
This solution worked for me in Swift 2.1. iOS 9.2.1. (Xcode 7.2) on iPhone in portrait Mode. I have tested it on the simulators iPhone 5 and 6+ and it also worked.
import UIKit
class EVEMainNaviVC: UINavigationController
{
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
}
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = APP_BACKGROUND_COLOR
self.setupAppNaviagtionBar()
}
private func setupAppNaviagtionBar()
{
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.navigationBar.setHeight(55.0)
self.navigationBar.translucent = false
self.navigationBar.alpha = 1.0
self.navigationBar.barTintColor = UIColor.whiteColor()
let newBackButtonImageInset = UIEdgeInsetsMake(0, 0, -6, 0)
let newBackButtonImage = UIImage(named: "back")?.imageWithAlignmentRectInsets(newBackButtonImageInset)
self.navigationBar.backIndicatorImage = newBackButtonImage
self.navigationBar.backIndicatorTransitionMaskImage = newBackButtonImage
self.navigationBar.tintColor = CUSTOM_BUTTON_COLOR
}
}
}
I found the simplest solution I have ever seen. Just three things.
Override UINavigationBar and use it in your UINavigationController
let navigationController = UINavigationController(navigationBarClass: NavigationBar.self, toolbarClass: nil)
navigationController.viewControllers = [viewController]
Setup your indicator images:
backIndicatorImage = #imageLiteral(resourceName: "back")
backIndicatorTransitionMaskImage = #imageLiteral(resourceName: "back")
Implement layoutSubviews in your custom UINavigationBar class.
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { (view) in
if let imageView = view as? UIImageView {
if imageView.image == backIndicatorImage || imageView.image == backIndicatorTransitionMaskImage {
view.frame.origin.y = floor((frame.height - view.frame.height) / 2.0)
}
}
}
}
That is all. :)
My goal was to position the back button image without meddling with the subviews of the UINavigationItem. So, what I did was to create an extension for UIImage that adds a padding around of the image.
extension UIImage {
public func imageWith(padding: UIEdgeInsets) -> UIImage {
let origin = CGPoint(x: padding.left, y: padding.top)
let sizeWithPadding = CGSize(width: padding.left + size.width + padding.right, height: padding.top + size.height + padding.bottom)
UIGraphicsBeginImageContextWithOptions(sizeWithPadding, false, 0.0)
draw(in: CGRect(origin: origin, size: size))
let imageWithPadding = UIGraphicsGetImageFromCurrentImageContext() ?? self
UIGraphicsEndImageContext()
return imageWithPadding
}
}
For example: if you want to move the image to the top you add the corresponding padding to the bottom.
.imageWith(padding: UIEdgeInsets(top: 0, left: 0, bottom: 2, right: 0))

How to change the TabBarItem background color

I'm trying to change the background of a TabBarItem or to make a whole Image which take all the space.
Like the example below :
Do You have an Idea how to do that in swift
Swift:
//change icon and title color
UITabBar.appearance().tintColor = UIColor.redColor()
//change background default color
UITabBar.appearance().barTintColor = UIColor.blackColor()
//change selected background image
UITabBar.appearance().selectionIndicatorImage = UIImage(named: "tabSelected")
in swift 4.2 with iOS 12.1
func setUpSelectionIndicatorImage(withColors colors: [UIColor]) {
//Make selection indicator image from color and set it to tabbar
let singleTabWidth: CGFloat = self.tabBar.frame.size.width / CGFloat(self.tabBar.items?.count ?? 1)
let height = DeviceType.IS_IPHONE_X ? 55 : self.tabBar.frame.size.height
let singleTabSize = CGSize(width:singleTabWidth , height: height)
let edgeInsets = DeviceType.IS_IPHONE_X ? UIEdgeInsets(top: 1, left: 0, bottom: 0, right: 0) : .zero
self.tabBar.selectionIndicatorImage = UIImage.gradient(size: singleTabSize, colors: colors)?.resizableImage(withCapInsets: edgeInsets)
}
override func viewDidLayoutSubviews() {
let colors = [UIColor.white, UIColor.green]
setUpSelectionIndicatorImage(withColors: colors)
}

Resources