Skip to content

Instantly share code, notes, and snippets.

@davidlawson
Last active December 8, 2021 10:33
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save davidlawson/e9140d1e5cd533ff503c15111c1908df to your computer and use it in GitHub Desktop.
Save davidlawson/e9140d1e5cd533ff503c15111c1908df to your computer and use it in GitHub Desktop.
UIBarButtonItem with badge, Swift 4, iOS 9/10/11
import UIKit
public class BadgeBarButtonItem: UIBarButtonItem
{
@IBInspectable
public var badgeNumber: Int = 0 {
didSet {
self.updateBadge()
}
}
private let label: UILabel
required public init?(coder aDecoder: NSCoder)
{
let label = UILabel()
label.backgroundColor = .red
label.alpha = 0.9
label.layer.cornerRadius = 9
label.clipsToBounds = true
label.isUserInteractionEnabled = false
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = .white
label.layer.zPosition = 1
self.label = label
super.init(coder: aDecoder)
self.addObserver(self, forKeyPath: "view", options: [], context: nil)
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
self.updateBadge()
}
private func updateBadge()
{
guard let view = self.value(forKey: "view") as? UIView else { return }
self.label.text = "\(badgeNumber)"
if self.badgeNumber > 0 && self.label.superview == nil
{
view.addSubview(self.label)
self.label.widthAnchor.constraint(equalToConstant: 18).isActive = true
self.label.heightAnchor.constraint(equalToConstant: 18).isActive = true
self.label.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 9).isActive = true
self.label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -9).isActive = true
}
else if self.badgeNumber == 0 && self.label.superview != nil
{
self.label.removeFromSuperview()
}
}
deinit {
self.removeObserver(self, forKeyPath: "view")
}
}
@ErAshu
Copy link

ErAshu commented Jul 5, 2018

How to use this

@chhaileng
Copy link

It works, thank you ^^

@Captgrom
Copy link

Captgrom commented Feb 5, 2019

Many thanks!

@dinsarenkh
Copy link

It works, thank you ^^

how to use it

@ashishkanani
Copy link

It works, thank you.

@limaho
Copy link

limaho commented Apr 10, 2019

Hello guys, how to use it with Storyboard?

@Sulman431
Copy link

Sulman431 commented Apr 16, 2019

If someone wants to know about the steps to use this in .storyboard
here are the steps,

  1. Download "BadgeBarButtonItem.swift" and Drag/ Add it in scope of your project with xCode open.
  2. Tell your bar button to be an object of type BadgeBarButtonItem in Identity Inspector
  3. Then you can set some other properties from .storyboard going to Attributes Inspector

@ashishkanani
Copy link

how to use Programmatically?

@gotye78
Copy link

gotye78 commented Jul 8, 2019

Very nice thanks!

@fibonaccidesigning
Copy link

it is so simple...

set badgeNumber Property

ex.

let countValue = 3

self.buttonName.badgeNumber = countValue

@jaiom143
Copy link

thanks it works with swift 4.2

@jaiom143
Copy link

We can use it as

let badgeBtn = BadgeBarButtonItem(image: UIImage(named: "notification"), style: .plain, target: self, action: #selector(notificationBtnClick))
badgeBtn?.badgeNumber = 4

    self.navigationItem.rightBarButtonItems = [badgeBtn,barBtn]

@sekharbethalam
Copy link

The above code is giving error like "Extra argument 'image' in call "

@sagarsnehi
Copy link

The above code is giving error like "Extra argument 'image' in call "

Copy link

ghost commented Apr 3, 2020

The above code is giving error like "Extra argument 'image' in call "

@zeeshanz
Copy link

zeeshanz commented May 8, 2020

Thanks for this code. There was one problem, that it won't work for numbers greater than 9. I had to modify the code a bit like this:

        let size = CGFloat(badgeNumber < 10 ? 18 : 26)
        let radius = CGFloat(badgeNumber < 10 ? 9 : 13)

        self.label.layer.cornerRadius = radius
        self.label.widthAnchor.constraint(equalToConstant: size).isActive = true
        self.label.heightAnchor.constraint(equalToConstant: size).isActive = true
        self.label.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: size/2).isActive = true
        self.label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -size/2).isActive = true

And removed the line self.label.layer.cornerRadius = 9 from required public init

@sami79031
Copy link

sami79031 commented Nov 27, 2020

Great job! Here is a version that keeps the UIBarButtonItem constructors intact and there is no need for observers.

public class BadgeBarButtonItem: UIBarButtonItem
{
    @IBInspectable
    public var badgeNumber: Int = 0 {
        didSet {
            self.updateBadge()
        }
    }
    
    private lazy var label: UILabel = {
        let label = UILabel()
        label.backgroundColor = .red
        label.alpha = 0.9
        label.layer.cornerRadius = 9
        label.clipsToBounds = true
        label.isUserInteractionEnabled = false
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textAlignment = .center
        label.textColor = .white
        label.layer.zPosition = 1
        return label
    }()
    
    private func updateBadge()
    {
        guard let view = self.value(forKey: "view") as? UIView else { return }
        
        self.label.text = "\(badgeNumber)"
        
        if self.badgeNumber > 0 && self.label.superview == nil
        {
            view.addSubview(self.label)
            
            self.label.widthAnchor.constraint(equalToConstant: 18).isActive = true
            self.label.heightAnchor.constraint(equalToConstant: 18).isActive = true
            self.label.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 9).isActive = true
            self.label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -9).isActive = true
        }
        else if self.badgeNumber == 0 && self.label.superview != nil
        {
            self.label.removeFromSuperview()
        }
    }
}


You can use it this way:

let item = BadgeBarButtonItem(image: UIImage(named: "some_image")),
                                   style: .plain,
                                   target: self,
                                   action: #selector(someAction))

navigationItem.rightBarButtonItem = item

@dan085
Copy link

dan085 commented Oct 9, 2021

not work!

@dan085
Copy link

dan085 commented Oct 9, 2021

not show bubble

@mudassir-abbas-brainx
Copy link

i know why bubble is not showing reason

guard let view = self.value(forKey: "view") as? UIView else { return }

this statement is only called with else condition

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment