Adding a Badge to UIBarButtonItems

Many app nowadays display badges over either a tab bar icon or navigation bar button but unfortunately , though quite common, this is not a feature provided by UIKit.

Since I needed such feature for a small project I’ve been working on lately I decided to implement it myself… and this post is about it!

First thing to do is thinking about the very nature of a badge: a number inscribed within a circle (so basically a shape an some text). Both can be rendered with the only help of CALayers which is great because it is the fastest an most lightweight way of drawing stuff on screen.

As a general idea of what Id like to achieve is having a way to add a badge do any UIBarButtonItem and be as well able to customize some of its characteristics:

Badge color

If it is filled or not (ex: if color is red then if filled the badges’ background color is red otherwise only the border is red and the background is white)

Let’s then begin writing the code to draw a circle. A circle is a shape which means we are going to use a CAShapeLayer. Insteas of sublclassing a CAShapeLayer creating a “CircleLayer” class let’s just write an extension instead to simply “shape” a generic CAShapeLayer:

In the code above we just provided to the class CAShapeLayer a method called drawCircleAtLocation that shape the layer into a circle. To analyze a little bit better:

based on the value of the “filled” input parameter we decide if the circle will have a colored background or a white background with a colored border

we define the path (the shape) of the CALayer using a UIBezierPath and its position within the button/icon

The other basic brick of our badge is another CALayer subclass: CATextLayer which for our purposes it is fine as it is without the need of subclassing nor extending. All we need to do is to assign it a string value (the number we want to display), other basic info, like size, alignment or color and we are done:

In the guard statement at the beginning of the method addBadge you can see that the UIBarButtonItem‘s view was retrieved using KVC (Key-Value Coding) instead of just accessing a property… that’s because there is no “view” property exposed therefore the only way to get it is KVC. And we need the view to be able to add the badge to it (as a sublayer of the main view layer).

Another thing to keep in ming is that the offset of the badge is evaluated from the upper right corner of the UIBarButtonItem’s view. Thus an offset of CGPoint(x: 10, y: 10) will basically add 10 pixels of margin on the right of the badge and 10 pixels of margin on the top.

With the code above we are now able to display a badge with a single digit number inscribed (a double digit number wouldn’t fit perfectly unless size and position were changed but for the my current needs, and for this post purpose, one digit is enough)… but what if I want to remove or change the badge (update the number)? I could iterate over all the sublayers of the UIBarButtonItem and look for a CAShapeLayer with a CATextLayer inside, but that would be neither practical nor safe!

What we can do is to tap into the power of the Objective-C runtime and use the Associated Objects!

Associated Objects are a feature of the Objective-C 2.0 runtime which allows objects to associate arbitrary values for keys at runtime… basically it allows to add custom properties to existing classes in extensions (or categories as they are known in Objective-C).

Exploiting this great feature we can now add a badge property to a UIBarButtonItem class to be able to easly modify or remove it: