Instantly share code, notes, and snippets.

Embed
What would you like to do?
//
// UIBarButtonItem+Badge.swift
// PiGuardMobile
//
// Created by Stefano Vettor on 12/04/16.
// Copyright © 2016 Stefano Vettor. All rights reserved.
//
import UIKit
extension CAShapeLayer {
private func drawCircleAtLocation(location: CGPoint, withRadius radius: CGFloat, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.CGColor : UIColor.whiteColor().CGColor
strokeColor = color.CGColor
let origin = CGPoint(x: location.x - radius, y: location.y - radius)
path = UIBezierPath(ovalInRect: 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) {
return b as? CAShapeLayer
} else {
return nil
}
}
func addBadge(number number: Int, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.redColor(), andFilled filled: Bool = true) {
guard let view = self.valueForKey("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, 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.whiteColor().CGColor : color.CGColor
label.backgroundColor = UIColor.clearColor().CGColor
label.contentsScale = UIScreen.mainScreen().scale
badge.addSublayer(label)
// Save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
func updateBadge(number number: Int) {
if let text = badgeLayer?.sublayers?.filter({ $0 is CATextLayer }).first as? CATextLayer {
text.string = "\(number)"
}
}
func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
@notoriousturtle

This comment has been minimized.

notoriousturtle commented Oct 18, 2016

Updated for Swift 3. It would be awesome if someone could make this support more than 1 number in the badge.

import UIKit

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()
    }
}
@austimkelly

This comment has been minimized.

austimkelly commented Nov 30, 2016

This one supports double digits, but not triple. Just a small change on the badge width and label offset, but I'll post the whole thing:

import UIKit

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()
        
        var badgeWidth = 8
        var numberOffset = 4
        
        if number > 9 {
            badgeWidth = 12
            numberOffset = 6
        }
        
        // 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 - CGFloat(numberOffset), y: offset.y), size: CGSize(width: badgeWidth, 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()
    }
}
@vijaymason

This comment has been minimized.

vijaymason commented Dec 11, 2016

Badge don't seem to work on UIBarButtonItem that are on UIToolbar, any ideas?

@dubayb

This comment has been minimized.

dubayb commented Jan 5, 2017

I've been searching for nearly a full day and implemented several others' attempts..This one is the best and easiest way!!! Thank you so much!!

@mctoys

This comment has been minimized.

mctoys commented Jan 12, 2017

I hate to ask, but how do I use this? I am new to IOS development. I have added the swift file and created a navigation button like this:

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(lockedButton))

Not sure where I can use the addBadge method? I tried referencing it like this:

navigationItem.rightBarButtonItem = UIBarButtonItem.addBadge()

But there is a red line through the addBadge option as I type, and it doesn't like it if I add it in manually...

@mctoys

This comment has been minimized.

mctoys commented Jan 12, 2017

Ok, sorry, I found the answer. For anyone else looking, you need to reference the button in a variable and add the method on that...

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(lockedButton))

	let rightBarButtons = self.navigationItem.rightBarButtonItems
	
	let lastBarButton = rightBarButtons?.last
	
	lastBarButton?.addBadge(number: 4)
@mglasgow

This comment has been minimized.

mglasgow commented Jan 29, 2017

This was a very useful starting point. I've taken the Swift 3 version and enhanced the function so it supports text (not just numbers). It also draws a rounded rectangle instead of a circle, to better support wider strings. The code dynamically calculates the badge size based on the badge text.

Finally, I've changed the signature to be setBadge. If the badge is null or empty, then the badge will be removed.

import UIKit

extension CAShapeLayer {
    func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
        fillColor = filled ? color.cgColor : UIColor.white.cgColor
        strokeColor = color.cgColor
        path = UIBezierPath(roundedRect: rect, cornerRadius: 7).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 setBadge(text: String?, withOffsetFromTopRight offset: CGPoint = CGPoint.zero, andColor color:UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11)
    {
        badgeLayer?.removeFromSuperlayer()
        
        if (text == nil || text == "") {
            return
        }
        
        addBadge(text: text!, withOffset: offset, andColor: color, andFilled: filled)
    }
    
    private func addBadge(text: String, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11)
    {
        guard let view = self.value(forKey: "view") as? UIView else { return }
        
        var font = UIFont.systemFont(ofSize: fontSize)
        
        if #available(iOS 9.0, *) {
            font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: UIFontWeightRegular)
        }
        
        let badgeSize = text.size(attributes: [NSFontAttributeName: font])
        
        // Initialize Badge
        let badge = CAShapeLayer()

        let height = badgeSize.height;
        var width = badgeSize.width + 2 /* padding */
        
        //make sure we have at least a circle
        if (width < height) {
            width = height
        }

        //x position is offset from right-hand side
        let x = view.frame.width - width + offset.x
        
        let badgeFrame = CGRect(origin: CGPoint(x: x, y: offset.y), size: CGSize(width: width, height: height))
        
        badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
        view.layer.addSublayer(badge)
        
        // Initialiaze Badge's label
        let label = CATextLayer()
        label.string = text
        label.alignmentMode = kCAAlignmentCenter
        label.font = font
        label.fontSize = font.pointSize
        
        label.frame = badgeFrame
        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)
    }
    
    private func removeBadge() {
        badgeLayer?.removeFromSuperlayer()
    }
}
@tosbaha

This comment has been minimized.

tosbaha commented Feb 14, 2017

I have tested with Xcode 8 and Swift 3. When device orientation changes, badge is removed. I checked the view hierarchy and observed that badge is gone. I hope someone writes a fix for that.

@RTimal

This comment has been minimized.

RTimal commented Mar 17, 2017

Thanks guys, very useful.

@iahmedhendi

This comment has been minimized.

iahmedhendi commented Apr 23, 2017

@tosbaha
if you put the code of addBadge inside viewDidLayoutSubviews() it will work fine with orientation

@Adil-Yousuf

This comment has been minimized.

Adil-Yousuf commented May 4, 2017

thanks its work fine.

@ugenlik

This comment has been minimized.

ugenlik commented Jun 20, 2017

I needed same thing for a regular UIButton,

extension UIButton
{
    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 , addedView:UIView?) {
        guard let view = addedView else { return }
        
        badgeLayer?.removeFromSuperlayer()
        
        var badgeWidth = 8
        var numberOffset = 4
        
        if number > 9 {
            badgeWidth = 12
            numberOffset = 6
        }
        
        // 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 - CGFloat(numberOffset), y: offset.y), size: CGSize(width: badgeWidth, 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 UIButtonItem 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()
    }

}

then wherever

    override func viewDidLoad() {
        super.viewDidLoad()
        button.tag = tagNumber   
    }

then

                  if let foundView = self.button.superView.viewWithTag(tagNumber) {
                    self.button.addBadge(number: 2, addedView: foundView)
                }
@sebastienboulogne

This comment has been minimized.

sebastienboulogne commented Aug 22, 2017

Hi all,

after doing this in viewDidLoad (and adding the extension code), i can see the bar button item in the navigation bar but not the badge :(

let inboxButton:UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "inbox"), style: .plain, target: self, action: #selector(presentInbox))
let point = CGPoint(x: 0, y: 0)
inboxButton.addBadge(number: 2, withOffset: point, andColor: UIColor.black, andFilled: false)
self.navigationItem.leftBarButtonItem = inboxButton

Has someone a simple working example? Thank you very much Guys :)

@mohn93

This comment has been minimized.

mohn93 commented Sep 3, 2017

https://gist.github.com/mohn93/bd950eeed82749b2ecad40ba3d4d0f1d

This fork has more features, hope it helps

@febinfathah

This comment has been minimized.

febinfathah commented Oct 22, 2017

Swift 4 version of the same

import UIKit

extension CAShapeLayer {
    func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
        fillColor = filled ? color.cgColor : UIColor.white.cgColor
        strokeColor = color.cgColor
        path = UIBezierPath(roundedRect: rect, cornerRadius: 7).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 setBadge(text: String?, withOffsetFromTopRight offset: CGPoint = CGPoint.zero, andColor color:UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11)
    {
        badgeLayer?.removeFromSuperlayer()
        
        if (text == nil || text == "") {
            return
        }
        
        addBadge(text: text!, withOffset: offset, andColor: color, andFilled: filled)
    }
    
    private func addBadge(text: String, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11)
    {
        guard let view = self.value(forKey: "view") as? UIView else { return }
        
        var font = UIFont.systemFont(ofSize: fontSize)
        
        if #available(iOS 9.0, *) {
            font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: UIFont.Weight.regular)
        }
        
        let badgeSize = text.size(withAttributes: [NSAttributedStringKey.font: font])
        
        // Initialize Badge
        let badge = CAShapeLayer()
        
        let height = badgeSize.height;
        var width = badgeSize.width + 2 /* padding */
        
        //make sure we have at least a circle
        if (width < height) {
            width = height
        }
        
        //x position is offset from right-hand side
        let x = view.frame.width - width + offset.x
        
        let badgeFrame = CGRect(origin: CGPoint(x: x, y: offset.y), size: CGSize(width: width, height: height))
        
        badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
        view.layer.addSublayer(badge)
        
        // Initialiaze Badge's label
        let label = CATextLayer()
        label.string = text
        label.alignmentMode = kCAAlignmentCenter
        label.font = font
        label.fontSize = font.pointSize
        
        label.frame = badgeFrame
        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)
    }
    
    private func removeBadge() {
        badgeLayer?.removeFromSuperlayer()
    }
}
@scurioni

This comment has been minimized.

scurioni commented Nov 3, 2017

Has anyone been able to use this with xcode 9?

This code does not work anymore:
guard let view = self.value(forKey: "view") as? UIView else { return }

@smifsud

This comment has been minimized.

smifsud commented Nov 5, 2017

works for me 9.0.1

@zeeip6

This comment has been minimized.

zeeip6 commented Nov 13, 2017

** #guard let view = self.value(forKey: "view") as? UIView else { return }** here view is getting value nil because of which it does not show badge how do i fix this?

@nithingwl

This comment has been minimized.

nithingwl commented Nov 22, 2017

@zeeip6, I faced the same issue. It guess the name of the key for view might be changed to something else and because of this its returning nil. The workaround I did for that was to use customView as below

guard let view = self.customView else { return }

and initialize a BarButtonItem by giving an UIButton as its custom view.

let bellButton = UIButton(type: .custom)
bellButton.setBackgroundImage(UIImage(named:"notification"), for: .normal)
bellButton.addTarget(self, action: #selector(didClickNotificationButton(_:)), for: .touchUpInside)

let notificationBarButton = UIBarButtonItem(customView: bellButton)

Note: Offset needs to be adjusted as required when adding a badge.

@piotrros

This comment has been minimized.

piotrros commented Nov 28, 2017

@febinfathah code is working, but in my case badge was a truncated a bit from the top and I had to add an offset, not a big deal. But then I realized that badge is drawing behind icon, which is unacceptable. I fixed it by adding the following line on the bottom off "addBadge":

badge.zPosition = 1000

So, the new code is:

import UIKit

extension CAShapeLayer {
    
    func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
        fillColor = filled ? color.cgColor : UIColor.white.cgColor
        strokeColor = color.cgColor
        path = UIBezierPath(roundedRect: rect, cornerRadius: 7).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 setBadge(text: String?, withOffsetFromTopRight offset: CGPoint = CGPoint.zero, andColor color:UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11) {
        badgeLayer?.removeFromSuperlayer()
        
        if (text == nil || text == "") {
            return
        }
        
        addBadge(text: text!, withOffset: offset, andColor: color, andFilled: filled)
    }
    
    private func addBadge(text: String, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11) {
        guard let view = self.value(forKey: "view") as? UIView else { return }
        
        var font = UIFont.systemFont(ofSize: fontSize)
        
        if #available(iOS 9.0, *) {
            font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: UIFont.Weight.regular)
        }
        
        let badgeSize = text.size(withAttributes: [NSAttributedStringKey.font: font])
        
        //initialize Badge
        let badge = CAShapeLayer()
        
        let height = badgeSize.height;
        var width = badgeSize.width + 2 /* padding */
        
        //make sure we have at least a circle
        if (width < height) {
            width = height
        }
        
        //x position is offset from right-hand side
        let x = view.frame.width - width + offset.x
        
        let badgeFrame = CGRect(origin: CGPoint(x: x, y: offset.y), size: CGSize(width: width, height: height))
        
        badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
        view.layer.addSublayer(badge)
        
        //initialiaze Badge's label
        let label = CATextLayer()
        label.string = text
        label.alignmentMode = kCAAlignmentCenter
        label.font = font
        label.fontSize = font.pointSize
        
        label.frame = badgeFrame
        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)
        
        //bring layer to front
        badge.zPosition = 1000
    }
    
    private func removeBadge() {
        badgeLayer?.removeFromSuperlayer()
    }
    
}

Tested on both iPhone and iPad with iOS 11.1. Maintains its appearance after changing orientation as well.

@johnxy84

This comment has been minimized.

johnxy84 commented Nov 30, 2017

Hello guys, I was trying to implement this using Xamarin but I always stop here because the view returned is always null. Am I missing out something or do I not understand how swift works? Here's where the error is and my c# implementation of that same code.
Swift
guard let view = self.value(forKey: "view") as? UIView else { return }
C#
var view = this.ValueForKey((Foundation.NSString)"view") as UIView;
if (view==null) return;

@khusro017

This comment has been minimized.

khusro017 commented Dec 5, 2017

Hi,
Any of you have solved this.
guard let view = self.value(forKey: "view") as? UIView else { return }
This always returns a null value (Swift 3).
thank you.

@kdieu001

This comment has been minimized.

kdieu001 commented Dec 13, 2017

guard let view = self.value(forKey: "view") as? UIView else { return }
This code will always return nil, if you set the badge in the wrong spot.

So if you want to display the badge, you would have to set the badge outside of viewDidLoad and you should see the badge. It seems setting the badge in viewDidAppear works for me there.

If you have been setting the badge in viewDidLoad, in iOS11 it handles it differently. Any related UI work will break or wont work. Try setting the badge outside of viewDidLoad and see if that works.

I hope this helps

@mak21

This comment has been minimized.

mak21 commented Jan 12, 2018

As others have mentioned the view will be nil if you set the badge on viewDidLoad on iOS 11 and Xcode 9. To solve this just set the badge to the bar button Item in the " viewDidAppear" function.

@Hexfire

This comment has been minimized.

Hexfire commented Jan 15, 2018

Thank you all for the solution, neat and concise.

@Sega-Zero

This comment has been minimized.

Sega-Zero commented Jan 25, 2018

Instead of global handle var, it is better to use static struct, IMHO

private struct AssociatedKey {
        static var badgeLayer = "badgeLayer"
    }

    fileprivate var badgeLayer: CAShapeLayer? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKey.badgeLayer) as? CAShapeLayer
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKey.badgeLayer,
                    newValue as CAShapeLayer?,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )
            }
        }
    }
@Sega-Zero

This comment has been minimized.

Sega-Zero commented Jan 25, 2018

Does anyone know why this doesn't work with back button?

@dipeshparashar

This comment has been minimized.

dipeshparashar commented Jan 26, 2018

Its not working in Swift4.

@basememara

This comment has been minimized.

basememara commented Jan 30, 2018

Different approach and works with Swift 4 and various view controller states: https://gist.github.com/yonat/75a0f432d791165b1fd6

@mukulm24

This comment has been minimized.

mukulm24 commented Jul 5, 2018

I have used this for tabItem but I want to show the layer on top of tabImage but layer is added behind it. Any way through which it can be attained?

@Dellybro

This comment has been minimized.

Dellybro commented Jul 18, 2018

This doesn't work unless it is a custom uibarbutton unforunately. :(

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