Skip to content

Instantly share code, notes, and snippets.

@davidlawson
Last active December 8, 2021 10:33
Show Gist options
  • 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")
}
}
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