Blog

Working with PaintCode and Interface Builder in XCode

04 Apr, 2015

Every self-respecting iOS developer should know about PaintCode by now, an OSX app for drawing graphics that don’t save as images, but as lengths of code that draw graphics. The benefits of this are vastly reduced app installation size – no need to include three resolutions of the same image for every image – and seamlessly scalable graphics.
One thing that I personally struggled with for a while now was how to use them effectively in combination with Interface Builder, the UI development tool for iOS and OSX apps. In this blog I will explain an effective and simple method to draw PaintCode graphics in a way where you can see what you’re doing in Interface Builder, using the relatively new @IBDesignable annotation. I will also go into setting colors, and finally about how to deal with views that depend on dynamic runtime data to draw themselves.

First, let’s create a simple graphic in PaintCode. Let’s draw a nice envelope or email icon. Add a frame around it, and set the constraints to variable so it will adjust to the size of the frame (drag the frame corners around within PaintCode to confirm). This gives us a nice, scalable icon. Save the file, then export it to your xcode project in the language of your choice.

Email icon in PaintCode

Squiggly lines are the best


 
Now, the easiest and most straightforward way to get a PaintCode image into your Interface Builder is to simply create a UIView subclass, and call the relevant StyleKit method in its drawRect method. You can do this too in either Swift or Objective-C; this example will be in Swift, but if you’re stuck with the slow Swift compiler in XCode 6.x, you might prefer Objective-C for this kind of simple and straightforward code.
To use it in Interface Builder, simply create a storyboard or xib, drag an UIView onto it, and change its Custom Class to your UIView subclass. Make sure to have your constraints set properly, and run – you should be able to see your custom icon in your working app.
[objc]
class EmailIcon: UIView {
override func drawRect(rect: CGRect) {
StyleKit.drawEmail(frame: rect)
}
}
[/objc]
Screenshot 2015-04-03 21.36.42

Ghost views, the horror of Interface Builder


 
As you probably noticed though, it’s far from practical as it is in Interface Builder right now – all you see (or don’t see really) is an empty UIView. I guess you could give it a background color in IB and unset that again in your code, but that’s far from practical. Instead, we’ll just slap an @IBDesignable annotation onto the UIView subclass. Going back to IB, it should start to compile code, and a moment later, your PaintCode image shows up, resizable and all.
[objc]
@IBDesignable
class EmailIcon: UIView {
override func drawRect(rect: CGRect) {
StyleKit.drawEmail(frame: rect)
}
}
[/objc]
@IBDesignable view in Interface Builder

like magic


 

Adding color

We can configure our graphic in PaintCode to have a custom color. Just go back to PaintCode and change the Color to ‘Parameter’ – see image.
Set color to Parameter
Export the file again, and change the call to the StyleKit method to include the color. It’s easiest in this case to just pass the UIView‘s own tintColor property. Going back to Interface Builder, we can now set the default tintColor property, and it should update in the IB preview instantly.
As an alternative, you can add an @IBInspectable color property to your UIView, but I would only recommend this for more complicated UIViews – if they include multiple StyleKit graphics, for example.
[objc]
@IBDesignable
class EmailIcon: UIView {
override func drawRect(rect: CGRect) {
StyleKit.drawEmail(frame: rect, color: tintColor)
}
}
[/objc]

let’s make it Xebia purple, to please the boss


 

Dealing with dynamic properties

One case I had to deal with is an UIView that can draw a selection of StyleKit images, depending on the value of a dynamic model value. I considered a few options to deal with that:

  1. Set a default value, to be overridden at runtime. This is a bit dangerous though; forget to reset or unset this property at runtime and your user will be stuck with some default placeholder, or worse, flat-out wrong information.
  2. Make the property @IBInspectable. This only works with relatively simple values though (strings, numbers, colors), and it has the same problem as the above.

What I needed was a way to set a property, but only from within Interface Builder. Luckily, that exists, as I found out later. In UIView, there is a method called prepareForInterfaceBuilder, which does exactly what it says on the tin – override the method to set properties only relevant in Interface Builder. So in our case:
[objc]
@IBDesignable
class EmailIcon: UIView {
// none set by default
var iconType: String? = nil
override func drawRect(rect: CGRect) {
if iconType == "email" {
StyleKit.drawEmail(frame: rect, color: tintColor)
}
if iconType = "email-read" {
StyleKit.drawEmailRead(frame: rect, color: tintColor)
}
// if none of the above, draw nothing
}
// draw the ’email’ icon by default in Interface Builder
override func prepareForInterfaceBuilder() {
iconType = "email"
}
}
[/objc]
And that’s all there is to it. You can use this method to create all of your graphics, keep them dynamically sized and colored, and to use the full power of Interface Builder and PaintCode combined.

Freek is an allround developer whose major focus has been on Javascript and large front-end applications for the past couple of years, building web and mobile web applications in Backbone and AngularJS.
guest
6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Andrew
Andrew
6 years ago

Hi there,
Great tutorial! I only recently bought PaintCode, and developing visually is certainly a big change for me.
I have a problem though: @IBDesignables never consistently work for me. I always get a rendering error, and even if my custom view does actually render, it breaks when I make any modifications (thereby debating the purpose).
Do you have any idea why this might be? I’ve cleaned my project every which way, with no success.
Here’s the error I receive:
IB Designables: Failed to render instance of CustomView: Rendering the view took longer than 200 ms. Your drawing code may suffer from slow performance.
I’d appreciate your help!
Thanks

Andrew
Andrew
6 years ago
Reply to  Freek Wielstra

Hey Freek,
I appreciate your reply; I didn’t get notified so I apologize for the delay (might’ve entered something wrong).
I’m not quite sure what code would be helpful, but here’s what I’ve got:
http://pastebin.com/Jc4w5V2e
And here’s a screenshot of the specific errors:
http://i.imgur.com/qmiNepO.png
If there’s anything else that would be helpful, please let me know.
Thanks!

Andrew
Andrew
6 years ago
Reply to  Freek Wielstra

Hi,
So I removed the duplicate code, and the 1st error went away, which is good. But the rendering error (the 2nd error) is still there.
Have you any idea what might cause that infinite loop? This is literally a *brand new* project, and those 2 files (along with the AppDelegate and the Storyboard) are really all I’ve got going on so far; should be squeaky clean.
It’s worth noting that I’m using Xcode Version 6.4 (6E35b) with the iOS 8.4 SDK.

Explore related posts