Adding a Badge to UIBarButtonItems

30 Apr

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)
  • its position within the UIBarButtonItem
  • the number to display

The result should be something like this:

Screen Shot 2016-04-28 at 08.45.40 not_filled_badge

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:

  1. 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
  2. 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 code above you can see that it is also managed the case filled/not filled giving the text a color based on the background (hence if background is filled text is clear and vice versa).

Now let’s put everything together in nice extention of UIBarButtonItem that will provide a method addBadge to draw a badge within it:

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:

 The class UIBarButtonItem has now a private computed variable to retrieve the badge layer (the property badgeLayer) and two other methods to interact with an already existing badge:
  • updateBadge: accepting in input the new number to display within the badge
  • removeBadge: method that completely remove the badge from the UIBarButtonItem’s view

Now that everything is in place all you need to display a badge on any UIBarButtonItem is to simply call the addBadge method whenever you want.

All the code used in this post is available as a GitHub Gist