Extending UITabBarItem with IBInspectable PaintCode icons

Written by Emil Loer on Mar 8, 2016 |

One of my most used tools outside of Xcode is an app called PaintCode. With PaintCode you can draw artwork that gets converted into Objective-C or Swift code to actually draw the art at runtime. This enables you to make great custom views and by making use of parameters you can even make the drawing code react to your application's state.

In this article I want to explore a particular example of how I use PaintCode in one of my iOS projects. In this project the root view controller is a UITabBarController whose tabs are extended to support PaintCode images.

If you look at Apple's apps you can see that a typical tab-based app has separate images for the selected and unselected states. These images then have to be available in 1x, 2x and 3x versions, meaning that we have to design a total of six images. And if there is a slight change in the design we have to recreate all six of them again. In my opinion this is a perfect situation for procedural images.

Creating a PaintCode canvas

The first thing we have to do is to create a canvas in PaintCode that contains our image. For this example I'm going to design a settings icon. The settings icon will use a style in which the selected state has a thicker outline. An alternative would be to change the icon's fill color which is perfectly possible using this approach too.

Let's start with creating the shape of the icon. Use a 25x25 canvas for the best results.

The shape of the icon

Now we want to create two states for this icon: the selected and the unselected state. For this we are going to introduce a boolean parameter:

The selected parameter

The parameter will be exposed to our image drawing method and we can use it in code to select which state we want to draw. However, the boolean value itself is not very useful to our shape, as we want to change the stroke width. So let's create a derived expression for this:

The stroke width expression

The variable width property now reflects the stroke width for the current selection state and we can use it in our icon as the stroke width for the outer shape.

The stroke width applied to the shape

Now when we tick the checkbox of the selected parameter we should see the icon changing between these two images:

The selected and unselected icons

The icon is now ready to be used. Make sure you export the image into a StyleKit using an image method in template mode:

StyleKit drawing method

Using the icon in UITabBarItem

Now that we have a procedurally generated icon we have to use it. Normally to do this one can subclass a view and override the drawRect: method to draw the StyleKit image from there. However, UITabBarItem is not a view and thus has no drawing methods.

From the documentation we can see that a tab bar item has an image and a selectedImage property and we can use these to provide the proper images. The simplest way to do this is to subclass the tab bar item like this:

class PaintCodeTabItem: UITabBarItem {
    override func awakeFromNib() {
        super.awakeFromNib()

        selectedImage = StyleKit.imageOfSettingsWithSelected(true)
        image = StyleKit.imageOfSettingsWithSelected(false)
    }
}

Of course you can omit the subclassing and create tab items procedurally from e.g. a view controller.

PaintCode also provides a way to set the images using outlets. This means you have to insert a StyleKit object inside every view controller that you want to access images from and drag two outlets per tab item. This feature works but is in my opinion not very flexible if you do a lot of Interface Builder work. For example, if you move views between view controllers you have to reconnect the outlets and such.

A smarter UITabBarItem

What I really wanted to do was to think of a solution that did not force me to write code for every custom tab image that I create and limit the work I have to do in Interface Builder. It turns out that Xcode has a very powerful feature called IBInspectable that we can leverage here. The general idea of inspectables is that you can tag a property to become available inside interface builder. This allows us to reuse a class for every icon and just type in the name of the icon inside the inspector.

Creating such a reusable class is not very straightforward because we have to dynamically decide which method of the StyleKit to call. That problem has already been solved though, so we can use the extractMethodFrom:selector: function from my previous article: Calling methods from strings in Swift.

Here's the implementation of the class:

class PaintCodeTabItem: UITabBarItem {
    /// The name of the PaintCode image in the form that is represented in the
    /// StyleKit method name, i.e. use "Settings" for "imageOfSettingsWithSelected:".
    @IBInspectable var name: String = ""

    override func awakeFromNib() {
        super.awakeFromNib()

        let selector = NSSelectorFromString("imageOf" + name + "WithSelected:")

        if let imageWithSelected = extractBoolMethodFrom(StyleKit.self, selector: selector) {
            selectedImage = imageWithSelected(true)
            image = imageWithSelected(false)
        } else {
            print("Warning: StyleKit does not respond to selector \(selector)")
        }
    }
}

Now when you assign the PaintCodeTabItem class to a UITabBarItem in Interface Builder you can see the following property in the inspector:

The inspectable tab item

And when we run our app we see the icon as designed in PaintCode:

The final icon

So there you have it! Simple and maintainable PaintCode tab bar icons. Of course you can extend this technique to any other user interface element. You can even create a UIView subclass that can call a StyleKit method by name in the drawRect: method. The possibilities are limitless!

If you've enjoyed this post please follow me on Twitter or Facebook. I'd really appreciate it!