7

Can I change the background color of a specific UITabBarItem in a UITabBar?

I know how to change all the background of the selected background, using:

[[UITabBar appearance] setBarTintColor:[UIColor redColor]];
[[UITabBar appearance] setTintColor:[UIColor blueColor]];
[[UITabBar appearance] setSelectedImageTintColor:[UIColor yellowColor]];

But can it be done for only one item without subclassing?

Thanks

ricardopereira
  • 11,118
  • 5
  • 63
  • 81
Hugues BR
  • 2,238
  • 1
  • 20
  • 26

6 Answers6

33

You can add a subview to the parent tabBar, and set a background color on the subview. You can use the tabBar frame dimensions to calculate the offset and width of your tabBarItem, and then insert the subview underneath.

Example (in Swift):

// Add background color to middle tabBarItem
let itemIndex = 2
let bgColor = UIColor(red: 0.08, green: 0.726, blue: 0.702, alpha: 1.0)

let itemWidth = tabBar.frame.width / CGFloat(tabBar.items!.count)
let bgView = UIView(frame: CGRectMake(itemWidth * itemIndex, 0, itemWidth, tabBar.frame.height))
bgView.backgroundColor = bgColor
tabBar.insertSubview(bgView, atIndex: 0)
ndbroadbent
  • 13,513
  • 3
  • 56
  • 85
  • Thank you @nathan.f77 it work, the only issue is that only allow me to change the background color (I've accepted your answer because that was actually my question). But as I change the background color, I also need change the tint of this item.. for now, I add a button on top on the tarBar at the item position... :( – Hugues BR Nov 06 '14 at 16:09
  • When I copy and paste this code, xCode gives an error: 'Could not find member frame' on the line: let bgView =... – Chris Harrison Dec 09 '14 at 07:15
  • @ChrisHarrison - that's weird, because you're calling `tabBar.frame` in the line above that, so it should be giving an error there as well. – ndbroadbent Dec 09 '14 at 18:36
  • @nathan.f77 Changing to this line: let itemWidth: CGFloat = tabBar.frame.width / CGFloat(tabBar.items!.count) solves the problem. – Chris Harrison Dec 10 '14 at 02:32
  • This is really clever. I thought insertSubview: will block the icon and the text. Turned out it didn't. – Di Wu Jan 22 '15 at 13:28
6

in Swift: This solution is for apps using Auto Layout . Main difference between other solution is that: tabBar.frame.width is not getting actual device's screen width. So, bgView is appearing wrong place.

You can fix it with this : UIScreen.main.bounds.width

let tabWidth: CGFloat = UIScreen.main.bounds.width / CGFloat(self.tabbar!.items!.count)
let tabIndex: CGFloat = 2
let bgColor: UIColor = .redColor
let bgView = UIView(frame: CGRectMake(tabWidth * tabIndex, 0, tabWidth, 49))
bgView.backgroundColor = bgColor
self.tabbar!.insertSubview(bgView, atIndex: 0)

49 is a default height of UITabbar

ricardopereira
  • 11,118
  • 5
  • 63
  • 81
fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
  • Not sure this is caused by a recent change to auto layout or not, but this will not work. Inserting the new view at index 0 in the last code line will have other subviews (most likely the background view) overwrite our new view. As with all view definitions the sequence of subviews matters for rendering. To put the new view above the background view but under image and text of tabIndex' item, this seems to fix things with iOS 9/Xcode 7.3: self.tabBar!.insertSubview(bgView, atIndex: tabIndex + 2) – marco Jul 17 '16 at 13:08
  • Perfect! Thanks! I confirm that It's working on iOS 12.1 and Xcode 10.1. – iHarshil Nov 15 '18 at 09:06
3

Objective C solution:

int itemIndex = 3;
UIColor* bgColor = [UIColor colorWithRed:(245/255.f) green:(192/255.f) blue:(47/255.f) alpha:1];

float itemWidth = self.tabBarController.tabBar.frame.size.width / 5.0f; //5 = tab bar items
UIView* bgView = [[UIView alloc]initWithFrame: CGRectMake(itemWidth*itemIndex, 0,itemWidth, self.tabBarController.tabBar.frame.size.height)];
bgView.backgroundColor = bgColor;
[self.tabBarController.tabBar insertSubview:bgView atIndex:1];
DaNLtR
  • 561
  • 5
  • 21
1

Updated for Swift 4:

let itemIndex: CGFloat = 2.0
let bgColor = UIColor.ownBlue

let itemWidth = tabBar.frame.width / CGFloat(tabBar.items!.count)
let bgView = UIView(frame: CGRect(x: itemWidth * itemIndex, y: 0, width: itemWidth, height: tabBar.frame.height))

bgView.backgroundColor = bgColor
tabBar.insertSubview(bgView, at: 0)
ricardopereira
  • 11,118
  • 5
  • 63
  • 81
makle
  • 1,237
  • 1
  • 15
  • 21
0

You cannot really do this with the tint color property. See this post about the fact that even the setSelectedImageTintColor does not really seem to be working (it did not last time I checked).

The solution is to change the tintColor on the fly for the item in question. You can do this ad hoc whenever the selected item changes by implementing the UITabBarDelegate method tabBar:didSelectItem: e.g. in the app delegate.

Community
  • 1
  • 1
Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Hey @Mundi, thx for your answer. I don't want to change it when it selected but all the time. I basically have five button all with white background except the one in the middle for which have a black background all the time. – Hugues BR Dec 06 '13 at 01:15
  • @HuguesBR did you find a way to do this? – knl May 24 '14 at 19:07
  • not really, I've added a uibutton to the tab bar view, which mask the tab in question to do the trick... – Hugues BR May 26 '14 at 04:25
0

Late to the game, but this is how I did it. I made the tabs look like buttons, with rounded views. The answers above, have issues when the device is rotated, or the app is sent into a split window.

This solution marries the background directly to each tab, using auto-layout, so they naturally follow the tab bar changes.

This is done on the custom subclass of UITabBarController.

First, I set the colors (white for unselected, and black for selected), of the icons in the tab items, then, I iterate the tabs, and insert backgrounds to each, using auto layout to have them cleave to the tabs:

override func viewDidLoad() {
    super.viewDidLoad()
    let appearance = UITabBarAppearance()
    appearance.stackedLayoutAppearance.normal.iconColor = .white
    appearance.stackedLayoutAppearance.selected.iconColor = .black
    appearance.inlineLayoutAppearance.normal.iconColor = .white
    appearance.inlineLayoutAppearance.selected.iconColor = .black
    appearance.compactInlineLayoutAppearance.normal.iconColor = .white
    appearance.compactInlineLayoutAppearance.selected.iconColor = .black

    tabBar.subviews.forEach {
        if let item = $0 as? UIControl {
            let wrapper = UIView()
            wrapper.isUserInteractionEnabled = false
            wrapper.clipsToBounds = true
            wrapper.backgroundColor = tabBar.tintColor
            wrapper.layer.cornerRadius = 8
            item.insertSubview(wrapper, at: 0)
            wrapper.translatesAutoresizingMaskIntoConstraints = false
            wrapper.leadingAnchor.constraint(equalTo: item.leadingAnchor).isActive = true
            wrapper.trailingAnchor.constraint(equalTo: item.trailingAnchor).isActive = true
            wrapper.topAnchor.constraint(equalTo: item.topAnchor).isActive = true
            wrapper.bottomAnchor.constraint(equalTo: item.bottomAnchor).isActive = true
        }
    }
}

The reason this works, is that the tab bar implements the tab items as an internal subclass of UIControl, which is a UIView.

I can add views underneath the control.

This ends up looking like this:

Sample Tab Bar

NB: Turning off user interaction is important, as is inserting at 0.

The drawback is that there's no way to tell which item is the selected one (at the time the background images are created).

ALSO NB: Apple REALLY doesn't want us mucking about in the tab bar, so I could see this method (as well as the ones above), breaking in some future OS upgrade.

Chris Marshall
  • 4,910
  • 8
  • 47
  • 72