Is it possible to customize UITabBarItem Badge?

17,222

Solution 1

This is your best bet. Add this extension at the file scope and you can customise the badges however you like. Just call self.tabBarController!.setBadges([1,0,2]) in any of your root view controllers.

To be clear that is for a tab bar with three items, with the badge values going from left to right.

If you want to add images instead just change the addBadge method

extension UITabBarController {
    
    func setBadges(badgeValues: [Int]) {

        var labelExistsForIndex = [Bool]()

        for _ in badgeValues {
            labelExistsForIndex.append(false)
        }

        for view in self.tabBar.subviews where view is PGTabBadge {
            let badgeView = view as! PGTabBadge
            let index = badgeView.tag
            
            if badgeValues[index] == 0 {
                badgeView.removeFromSuperview()
            }
            
            labelExistsForIndex[index] = true
            badgeView.text = String(badgeValues[index])
        }

        for i in 0...(labelExistsForIndex.count - 1) where !labelExistsForIndex[i] && (badgeValues[i] > 0) {
            addBadge(index: i, value: badgeValues[i], color: .red, font: UIFont(name: "Helvetica-Light", size: 11)!)
        }

    }

    func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {

        let itemPosition = CGFloat(index + 1)
        let itemWidth: CGFloat = tabBar.frame.width / CGFloat(tabBar.items!.count)

        let bgColor = color

        let xOffset: CGFloat = 5
        let yOffset: CGFloat = -12

        let badgeView = PGTabBadge()
        badgeView.frame.size =  CGSize(width: 12, height: 12)
        badgeView.center = CGPoint(x: (itemWidth * itemPosition) - (itemWidth / 2) + xOffset, y: 20 + yOffset)
        badgeView.layer.cornerRadius = badgeView.bounds.width/2
        badgeView.clipsToBounds = true
        badgeView.textColor = UIColor.white
        badgeView.textAlignment = .center
        badgeView.font = font
        badgeView.text = String(value)
        badgeView.backgroundColor = bgColor
        badgeView.tag = index
        tabBar.addSubview(badgeView)

    }
}
    
class PGTabBadge: UILabel { }

Solution 2

Here is another solution based on TimWhiting's answer:

extension UITabBar {
        func setBadge(value: String?, at index: Int, withConfiguration configuration: TabBarBadgeConfiguration = TabBarBadgeConfiguration()) {
            let existingBadge = subviews.first { ($0 as? TabBarBadge)?.hasIdentifier(for: index) == true }
            existingBadge?.removeFromSuperview()

            guard let tabBarItems = items,
                let value = value else { return }

            let itemPosition = CGFloat(index + 1)
            let itemWidth = frame.width / CGFloat(tabBarItems.count)
            let itemHeight = frame.height

            let badge = TabBarBadge(for: index)
            badge.frame.size = configuration.size
            badge.center = CGPoint(x: (itemWidth * itemPosition) - (0.5 * itemWidth) + configuration.centerOffset.x,
                                   y: (0.5 * itemHeight) + configuration.centerOffset.y)
            badge.layer.cornerRadius = 0.5 * configuration.size.height
            badge.clipsToBounds = true
            badge.textAlignment = .center
            badge.backgroundColor = configuration.backgroundColor
            badge.font = configuration.font
            badge.textColor = configuration.textColor
            badge.text = value

            addSubview(badge)
        }
    }

    class TabBarBadge: UILabel {
        var identifier: String = String(describing: TabBarBadge.self)

        private func identifier(for index: Int) -> String {
            return "\(String(describing: TabBarBadge.self))-\(index)"
        }

        convenience init(for index: Int) {
            self.init()
            identifier = identifier(for: index)
        }

        func hasIdentifier(for index: Int) -> Bool {
            let has = identifier == identifier(for: index)
            return has
        }
    }

    class TabBarBadgeConfiguration {
        var backgroundColor: UIColor = .red
        var centerOffset: CGPoint = .init(x: 12, y: -9)
        var size: CGSize = .init(width: 17, height: 17)
        var textColor: UIColor = .white
        var font: UIFont! = .systemFont(ofSize: 11) {
            didSet { font = font ?? .systemFont(ofSize: 11) }
        }

        static func construct(_ block: (TabBarBadgeConfiguration) -> Void) -> TabBarBadgeConfiguration {
            let new = TabBarBadgeConfiguration()
            block(new)
            return new
        }
    }

Solution 3

You can use a more robust solution @UITabbarItem-CustomBadge.

Demo

enter image description here

Simple two line of code can get you going

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  //supplying the animation parameter
  [UITabBarItem setDefaultAnimationProvider:[[DefaultTabbarBadgeAnimation alloc] init]];
  [UITabBarItem setDefaultConfigurationProvider:[[DefaultSystemLikeBadgeConfiguration alloc] init]];

  //rest of your code goes following...

  return YES;
}

Solution 4

TimWhiting's answer updated to Swift 4 with the removal of some force unwrapping.

extension UITabBarController {
  func setBadges(badgeValues: [Int]) {
    var labelExistsForIndex = [Bool]()

    for _ in badgeValues {
      labelExistsForIndex.append(false)
    }

    for view in tabBar.subviews {
      if let badgeView = view as? PGTabBadge {
        let index = badgeView.tag

        if badgeValues[index] == 0 {
          badgeView.removeFromSuperview()
        }

        labelExistsForIndex[index] = true
        badgeView.text = String(badgeValues[index])
      }
    }

    for i in 0 ... labelExistsForIndex.count - 1 where !labelExistsForIndex[i] && badgeValues[i] > 0 {
      addBadge(
        index: i,
        value: badgeValues[i],
        color: UIColor(red: 4 / 255, green: 110 / 255, blue: 188 / 255, alpha: 1),
        font: UIFont(name: "Helvetica-Light", size: 11)!
      )
    }
  }

  func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
    guard let tabBarItems = tabBar.items else { return }
    let itemPosition = CGFloat(index + 1)
    let itemWidth: CGFloat = tabBar.frame.width / CGFloat(tabBarItems.count)

    let bgColor = color

    let xOffset: CGFloat = 12
    let yOffset: CGFloat = -9

    let badgeView = PGTabBadge()
    badgeView.frame.size = CGSize(width: 17, height: 17)
    badgeView.center = CGPoint(x: (itemWidth * itemPosition) - (itemWidth / 2) + xOffset, y: 20 + yOffset)
    badgeView.layer.cornerRadius = badgeView.bounds.width / 2
    badgeView.clipsToBounds = true
    badgeView.textColor = UIColor.white
    badgeView.textAlignment = .center
    badgeView.font = font
    badgeView.text = String(value)
    badgeView.backgroundColor = bgColor
    badgeView.tag = index
    tabBar.addSubview(badgeView)
  }
}

class PGTabBadge: UILabel {}

Sample Image

enter image description here

Solution 5

well... changing the background of the built-in badge does not seem possible to me. But what IS possible instead, is the following:

Subclass the TabBarController, build a custom view laid out in a NIB, add it to the view hierarchy at the location in the tab bar, where you want it to be.

In the custom view you can set up an image as background and a label on top of that background, that will display the number value you can then change by code.

Then you need to figure out the horizontal and vertical location of your custom view where you want to place your view inside of the tab bar.

Hope this helps a little bit. Sebastian

Share:
17,222
Michelle Ybanez
Author by

Michelle Ybanez

Updated on July 17, 2022

Comments

  • Michelle Ybanez
    Michelle Ybanez almost 2 years

    The question below is similar as mine.

    How to use a custom UIImage as an UITabBarItem Badge?

    Is it possible to use a background image instead of drawing it myself? I think it's fine since my app will only use a 1-digit badgeValue.

    If it is not really possible, I want to know if we can change the badge color instead. The answers in the question below are not really helping.

    Is it possible to change UITabBarItem badge color

  • Michelle Ybanez
    Michelle Ybanez about 11 years
    Thank you for this idea. I will try this out. I have a follow-up question though. Is this badge customization alright if this app will be submitted to the AppStore?
  • sesc360
    sesc360 about 11 years
    You are welcome... please be so kind and accept the answer if you are satisfied. Well.... this is no problem because there is no need to use the badge provided by apple if you don't like it. You only get into trouble if you try do modify private classes provided by apple.
  • Fábio Oliveira
    Fábio Oliveira over 8 years
    Is this App Store safe?
  • TimWhiting
    TimWhiting over 8 years
    Absolutely, the tab bar is just a view, so adding subviews to it is totally legit.
  • Ratul Sharker
    Ratul Sharker over 7 years
    please leave a reason in comment if anyone gonna leave a -1
  • Ratul Sharker
    Ratul Sharker over 7 years
    by the way its a github repository of my own, should i add full 6 files of code ? are you okay with that ? @Zoleas
  • Jesse S.
    Jesse S. about 5 years
    Thanks! This isn't removing the badge though once the tab bar item with the badge is tapped. Any help would be appreciated. :)
  • Widerberg
    Widerberg almost 5 years
    @JesseS. You can manually remove it upon tapping by setting the badge's value to nil.
  • Kacy
    Kacy about 3 years
    You're a legend.