Swift add badge to navigation barButtonItem and UIButton

喜夏-厌秋 提交于 2019-12-02 22:00:52

Working Solution :

Step 1: Firstly create new swift file which is a subclass to UIButton as follows:

import UIKit

class BadgeButton: UIButton {

    var badgeLabel = UILabel()

    var badge: String? {
        didSet {
            addbadgetobutton(badge: badge)
        }
    }

    public var badgeBackgroundColor = UIColor.red {
        didSet {
            badgeLabel.backgroundColor = badgeBackgroundColor
        }
    }

    public var badgeTextColor = UIColor.white {
        didSet {
            badgeLabel.textColor = badgeTextColor
        }
    }

    public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
        didSet {
            badgeLabel.font = badgeFont
        }
    }

    public var badgeEdgeInsets: UIEdgeInsets? {
        didSet {
            addbadgetobutton(badge: badge)
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        addbadgetobutton(badge: nil)
    }

    func addbadgetobutton(badge: String?) {
        badgeLabel.text = badge
        badgeLabel.textColor = badgeTextColor
        badgeLabel.backgroundColor = badgeBackgroundColor
        badgeLabel.font = badgeFont
        badgeLabel.sizeToFit()
        badgeLabel.textAlignment = .center
        let badgeSize = badgeLabel.frame.size

        let height = max(18, Double(badgeSize.height) + 5.0)
        let width = max(height, Double(badgeSize.width) + 10.0)

        var vertical: Double?, horizontal: Double?
        if let badgeInset = self.badgeEdgeInsets {
            vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
            horizontal = Double(badgeInset.left) - Double(badgeInset.right)

            let x = (Double(bounds.size.width) - 10 + horizontal!)
            let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
            badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
        } else {
            let x = self.frame.width - CGFloat((width / 2.0))
            let y = CGFloat(-(height / 2.0))
            badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
        }

        badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
        badgeLabel.layer.masksToBounds = true
        addSubview(badgeLabel)
        badgeLabel.isHidden = badge != nil ? false : true
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.addbadgetobutton(badge: nil)
        fatalError("init(coder:) is not implemented")
    }
}

Step 2: Create a function in your base file which u can use in each View Controller :

  func addBadge(itemvalue: String) {

        let bagButton = BadgeButton()
        bagButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
        bagButton.tintColor = UIColor.darkGray
        bagButton.setImage(UIImage(named: "ShoppingBag")?.withRenderingMode(.alwaysTemplate), for: .normal)
        bagButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
        bagButton.badge = itemvalue
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: bagButton)
    }

Step 3 : Use above function from any View Controller in this way :

self.addBadge(itemvalue: localStorage.string(forKey: "total_products_in_cart") ?? "0")

There is a more elegant solution with an extension for UIButtonItem

extension CAShapeLayer {
    func drawCircleAtLocation(location: CGPoint, withRadius radius: CGFloat, andColor color: UIColor, filled: Bool) {
        fillColor = filled ? color.cgColor : UIColor.white.cgColor
        strokeColor = color.cgColor
        let origin = CGPoint(x: location.x - radius, y: location.y - radius)
        path = UIBezierPath(ovalIn: CGRect(origin: origin, size: CGSize(width: radius * 2, height: radius * 2))).cgPath
    }
}

private var handle: UInt8 = 0

extension UIBarButtonItem {
    private var badgeLayer: CAShapeLayer? {
        if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
            return b as? CAShapeLayer
        } else {
            return nil
        }
    }

    func addBadge(number: Int, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true) {
        guard let view = self.value(forKey: "view") as? UIView else { return }

        badgeLayer?.removeFromSuperlayer()

        // Initialize Badge
        let badge = CAShapeLayer()
        let radius = CGFloat(7)
        let location = CGPoint(x: view.frame.width - (radius + offset.x), y: (radius + offset.y))
        badge.drawCircleAtLocation(location: location, withRadius: radius, andColor: color, filled: filled)
        view.layer.addSublayer(badge)

        // Initialiaze Badge's label
        let label = CATextLayer()
        label.string = "\(number)"
        label.alignmentMode = kCAAlignmentCenter
        label.fontSize = 11
        label.frame = CGRect(origin: CGPoint(x: location.x - 4, y: offset.y), size: CGSize(width: 8, height: 16))
        label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
        label.backgroundColor = UIColor.clear.cgColor
        label.contentsScale = UIScreen.main.scale
        badge.addSublayer(label)

        // Save Badge as UIBarButtonItem property
        objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }

    func updateBadge(number: Int) {
        if let text = badgeLayer?.sublayers?.filter({ $0 is CATextLayer }).first as? CATextLayer {
            text.string = "\(number)"
        }
    }

    func removeBadge() {
        badgeLayer?.removeFromSuperlayer()
    }
}

This great code was created by Stefano Vettor and you can find all the details at: https://gist.github.com/freedom27/c709923b163e26405f62b799437243f4

using M13BadgeView.. use this code

(im using fontawesome.swift for buttons :: https://github.com/thii/FontAwesome.swift)

    let rightButton = UIButton(frame: CGRect(x:0,y:0,width:30,height:30))
    rightButton.titleLabel?.font = UIFont.fontAwesome(ofSize: 22)
    rightButton.setTitle(String.fontAwesomeIcon(name: .shoppingBasket), for: .normal)

    let rightButtonItem : UIBarButtonItem = UIBarButtonItem(customView: rightButton)

    let badgeView = M13BadgeView()
        badgeView.text = "1"
        badgeView.textColor = UIColor.white
        badgeView.badgeBackgroundColor = UIColor.red
        badgeView.borderWidth = 1.0
        badgeView.borderColor = UIColor.white
        badgeView.horizontalAlignment = M13BadgeViewHorizontalAlignmentLeft
        badgeView.verticalAlignment = M13BadgeViewVerticalAlignmentTop
        badgeView.hidesWhenZero = true

    rightButton.addSubview(badgeView)

    self.navigationItem.rightBarButtonItem = rightButtonItem

I had the same task. I didn't want to use third-party libraries. Firstly, I tried Stefano's solution and it's great however I decided to implement my own way to solve it.

In my humble opinion, there are simple steps described below briefly:

  1. Create UIView instance within .xib file and put necessary items like UILabel or UIImageView instance depending on your design requirements.

The final action I did in this step is putting invisible button in the top of view's hierarchy.

  1. Create YourCustomView.swift and link all @IBOutlets from xib to current file inside your custom view class implementation.

  1. Next, implement class function in YourCustomView class which will load custom view from xib and return it as YourCustomView instance.

  1. Finally, add your custom badge to your custom view controller instance!

My result is..

P.S. If you need to implement @IBActions I recommend to link your custom view and custom view controller through the delegate pattern.

Leandro

Good answer @Julio Bailon (https://stackoverflow.com/a/45948819/1898973)!

Here is the author's site with full explanation: http://www.stefanovettor.com/2016/04/30/adding-badge-uibarbuttonitem/.

It seems not to be working on iOS 11, maybe because the script try to access the "view" property of the UIBarButtonItem. I made it work:

  1. By creating a UIButton and then creating the UIBarButtonItem using the UIButton as a customView:

    navigationItem.rightBarButtonItem = UIBarButtonItem.init(
            customView: shoppingCartButton)
    
  2. By replacing the line in the UIBarButtonItem extension:

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

    with the following:

    guard let view = self.customView else { return }
    

Seems elegant to me and, best of all, it worked!

You can set below constraints to UILabel with respect to UIButton

align UILabel's top and trailing to UIButton

And when you need to show badge set text to UILabel and when you don't want to show badge then set empty string to UILabel

Answer with extension from Julio will not work.

Starting from iOS 11 this code will not work cause line of code below will not cast UIView. Also it's counting as private API and seems to be will not pass AppStore review.

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

Thread on Apple Developer Forum

Second thing that this snippet always draws circle, so it can't fit numbers bigger than 9.

Download This

For BarButtonItem : Drag and Drop UIBarButtonItem+Badge.h and UIBarButtonItem+Badge.m class in project.

Write this code for set Badges:

self.navigationItem.rightBarButtonItem.badgeValue = "2"
self.navigationItem.rightBarButtonItem.badgeBGColor = UIColor.black

For UIButtton : Drag and Drop UIButton+Badge.h and UIButton+Badge.m class in project.

self.notificationBtn.badgeValue = "2"
self.notificationBtn.badgeBGColor = UIColor.black
Claudia Fitero

The MIBadgeButton-Swift is working also on UIBarButtonItems. Here is my code after the navigation bar is created:

let rightBarButtons = self.navigationItem.rightBarButtonItems

let alarmsBarButton = rightBarButtons?.last

let alarmsButton = alarmsBarButton.customView as! MIBadgeButton?

alarmsButton.badgeString = "10"
Petro Nepyivoda

You can do it programmatically with

self.tabBarItem.badgeColor = .red

or use the storyboard. See:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!