Adding badges to iOS App Icons

TL;DR: I've created a bash script that adds badges to the iOS App Icons. You can find it as well as documentation on GitHub.

Sometimes you have to work an app that has multiple versions. If you keep all those version on a phone, you've probably ended up with something like this:

icons_without_badge

Where you have couple of identical icons and the only visible difference between apps is the postfix in app's name.
Now, admit how many times did you open the wrong version of the app without noticing it? How many times did you wonder why some features didn't work just to discover that you were using the wrong app? It happened to everyone - clients, QAs and developers.
Now look at this screen:

icons_with_badge

Can you see how easy it is to distinguish different app versions? Little, simple badge on the App Icon can save a lot of time.
That's why I created Badger - a script that adds badges to iOS App Icons.

Badger

Badger is a bash script that takes a badge image and adds it on top of iOS App Icons.

badger_icons-1

Script is really simple to use, all you need to do is run this command in Terminal:

Terminal
 ./badger.sh -b "/path/to/badge.png" -i "/path/to/Assets.xcassets/AppIcon.appiconset"

As you can see, it takes only two parameters. The first one -b is a path to your badge image. The second one -i points to .appiconset file, where all App Icons are stored.

How does it work?

Firstly, we must check whether your machine have all required tools.

badger.sh
 function checkTools() {
    PATH_TO_IMAGE_MAGIC=$(which composite)
    
    if [ -z $PATH_TO_IMAGE_MAGIC ]; then
        logError "You have to install ImageMagick to use 'badge'."
        echo "Install it using: 'brew install imagemagick'"
        exit 1
    fi
}

Right now, badger is relaying on only one external dependency - ImageMagic. This dependency is used to perform all underlying graphics operation. To check whether the ImagaMagic is installed, we try to obtain the path to the composite tool which is part of the ImagaMagic. If we can't find the tool, we inform user about missing dependency.

In the second step we try to obtain all information necessary to run the script. As this is the hardest to understand part of the script, I've broken it down into smaller fragments.
We start by setting default values to variables.

badger.sh
BADGE_IMAGE_PATH=""
ICONSET_PATH=""

Those two variables contains paths to badge image and .appiconset. Later on we will set proper file paths and use those variables to read the content of those files.

In the next step we try to read options (flags) -b and -i using getopt function.

badger.sh
OPTS=`getopt b:i: $*`
eval set -- "$OPTS"

if [ $? != 0 ] ; then
     reportUsage
     exit 1
fi

If user did not set any flag, we inform about proper usage.
Once we read those flags we try to extract values from those flags. Then we assign them to appropriate variables.

badger.sh

# Extract options and their arguments into variables.
while true ; do
    case "$1" in
        -b) BADGE_IMAGE_PATH=$2 ; shift 2 ;;
        -i) ICONSET_PATH=$2 ; shift 2 ;;
        --) shift ; break ;;
        *) reportUsage ; exit 1 ;;
    esac
done

Also, when we detect flag that is not supported, we inform user how to use the script.

At this point we should have all necessary informations. Now, it is time to validate provided data.

badger.sh
if [ -z $BADGE_IMAGE_PATH ]; then
    logError "Path to badge image is missing."
    reportUsage
    exit 1
fi

if ! [ -f "$BADGE_IMAGE_PATH" ]; then
    logError "Could not find file ${BADGE_IMAGE_PATH}"
    exit 1
fi

if [ -z $ICONSET_PATH ]; then
    logError "Path to .appiconset file is missing."
    reportUsage
    exit 1
fi

if ! [ -d "$ICONSET_PATH" ]; then
    logError "Could not find file ${ICONSET_PATH}"
    exit 1
fi

Validation is very simple. We check whether user provided both required paths. If so, we verify whether those paths points to real files on a disk.

The .appiconset extension might suggest that this is a file, but in fact it is a directory. Because of that, when we verify the $ICONSET_PATH we check whether it points to a directory.

When validation is completed, we can start adding badges to the icons.
In the first step we search for all icons that are saved in .appiconset.

badger.sh
ICONS=$(find -E "$ICONSET_PATH" -regex '.*\.(png|PNG)')

We use regex to obtain all .png files. As a result of this search we get list of paths to image files and we save this list in the $ICONS variable.

We only search for .png files because iOS App Icons are supported only in that format.

In the last step we iterate over each file from the list and we add the badge:

badger.sh
while read -r icon; do
    addBadge $icon "$BADGE_IMAGE_PATH" $icon
done <<< "$ICONS"

To add a badge to the icon file we use the addBadge function.

badger.sh
function addBadge() {
    ICON_PATH=$1
    BADGE_PATH=$2

    WIDTH=$(identify -format %w ${ICON_PATH})
    HEIGHT=$(identify -format %h ${ICON_PATH})
    SIZE="${WIDTH}x${HEIGHT}"
    
    composite -resize ${SIZE} ${BADGE_PATH} ${ICON_PATH} ${ICON_PATH}    
}

This function does all the magic. It takes 2 parameters: path to the icon and path to the badge file. As the first step, it reads the width and height of the icon. We need those dimensions, because we need to resize the badge image to the size of the icon. We do this to ensure that badge is fitted properly on top of the icon.

Then we use the composite tool to actually resize the badge and put it on top of the icon. The result of this operation is saved in path of the source icon, replacing original file with icon that contains the badge.

Summary

I've already used this script in couple of projects with great success. I've simply added this script as step in the build process that is run on the CI/CD tool.
Once everything has been set up, it definitely made my life easier. Hopefully it can help you as well. If you are interested, you can get the script from GitHub.