Home Archive Tutorials

PaintCode Tutorial: Custom Progress Bar

In this second part of our PaintCode tutorial series, learn how to make a custom progress bar drawn with Core Graphics – but without having to write any code!


  • Other, Other, Other

Welcome back to our PaintCode tutorial series!

PaintCode is a neat app where you can draw user interfaces like in Photoshop – but instead of generating an image, it generates Core Graphics code.

In the first part of the series, we showed you how you could use PaintCode to create beautiful resizable and recolorable buttons.

In this tutorial, you’ll create your own custom progress bar design using PaintCode, and then integrate it into your app.

It helps if you complete part 1 first, but if you would rather just skip that one and focus on progress bars, that is OK. This tutorial works fine standalone – just make sure you have PaintCode installed, and download this starter project I’ve created for you.

Without further ado, open up PaintCode to get started on your next dynamic UI control!

Getting Started

Open a new document in PaintCode, click File\Rename…, name it DynamicProgressIndicator, and hit return.

In this tutorial, you’ll be working on the non-retina version of the control. If you’d like to see the retina version at any time, click the Retina button, next to the Canvas button, at the bottom right corner of the PaintCode window.

If you have the code view open, the buttons will be above the white panel at the bottom, as shown in the screenshot below:

PaintCode canvas

Next, you’re going to change the canvas size to a smaller size more appropriate for the progress bar you’re creating, and also change the background color to a nice dark color that the control you’re about to make would look especially nice on.

To do this, click the Canvas button to make the canvas 320 pixels by 70 pixels.Change the Underlay color to a dark grey as shown in the screenshot below. If you want the exact color, set R=45, G=45, B=45, and A=255.

Canvas color change

Now that the canvas is ready, you’re all set to create the basic shapes, gradients, and colors of your progress bar.

Select the Round Rect tool from the toolbar and draw a rounded rectangle on your canvas to represent the outside box of your control. With the rounded rectangle shape selected, set the shape’s properties to the following values:

  • X : 2
  • Y : 1
  • Width : 316
  • Height : 34
  • Radius : 4

Your shape’s properties panel should look like the following:

Border rounded rectangle

With the rectangle still selected, find the Stroke section in the sidebar, and set it to No Stroke. Also, click the box next to Fill, choose Add New Gradient…, and click the left hand color stop. Double click the color, and set the RGB values to 82 82 82. Now click the right hand color stop and set the RGB values to 106 106 106. Rename the gradient Outer Rect Gradient.

Renaming the gradienet

Note: If you ever need to edit your PaintCode gradients, colors, or shadows, you can find them at the bottom of the left panel under the appropriately named tabs. Simply double click the name of an item to bring up the edit dialog for that particular element.

Close that dialog, and back in the rectangle settings look for the Outer Shadow section. Add a dark Outer Shadow with RGB values of 51 51 51. Name the shadow DarkShadow, as shown below:

PaintCode outer shadow

Next, add a light Inner Shadow with RGB values of 171 171 171. Name it LightShadow, as demonstrated in the following screenshot:

PaintCode inner shadow

Change the name of the Rounded Rectangle to Border. It’s always a good idea to give each element of your control a descriptive name so that you can easily identify each part later when you hook up the control in your app.

Renaming the Rectangle

Now that the basic outline of your progress bar is done, you’ll need to add the outline of the progress track.

Drawing the Track and Track Background

Click the Round Rect tool again and draw another rounded rectangle on your canvas. Set the new shape’s properties as follows:

  • X : 12
  • Y : 10
  • Width : 292
  • Height : 14
  • Radius : 7

Track rounded rectangle

Set the Fill of your shape as a new gradient with a left color stop of RGB 48 48 48 and a right color stop of RGB 63 63 63, as shown below:

PaintCode rectangle gradient

Set the Outer Shadow to LightShadow, the Inner Shadow to DarkShadow, and finally rename the shape to ProgressTrack.

At this point, your control should look like the following:

Indicator progress

The final visual component to add is the active progress track.

Choose the Round Rect tool again, drag out a rectangle and set its properties as follows:

  • X : 14
  • Y : 12
  • Width : 288
  • Height : 10
  • Radius : 5

This final shape will be 2 pixels in from the left edge of ProgressTrack and centered vertically.

Note: If you were to try to draw this shape out manually instead of typing in the numbers like you did here, you might find it hard to do because the progress track is so similar in size to the track background.

However, there are two things you could do in this situation (besides modifying the numbers like you did here): 1) Select the track background, copy it and paste it and modify the copy to be a bit smaller, or 2) Pinch-zoom on your trackpad to zoom in on the shape you’re working with so drawing is easier.

To make the active progress track stand out, change the Fill to a green color — RGB 0 226 0. Or if you prefer another color, feel free to choose your own!

Finally, change the Outer Shadow and Inner Shadow of your shape to No Shadow, remove the Stroke and name your shape ProgressTrackActive. Your control should now look like the following:

Green indicator track

It looks great — but to make it come alive, you need to add some components that will make your control respond dynamically.

Adding the Control Frame

Remember – any time you want to be able to easily resize an element that you create in PaintCode, you should put that element inside a frame, and set up PaintCode so it knows how to resize the element as you resize its parent frame.

There are three steps to do this:

  1. Group: Put any elements you want resized by a parent frame into a group
  2. Frame: Draw a frame, and make sure all those elements are inside. Set the Apply only to entirely enclosed shapes setting to make sure it only covers what’s inside.
  3. Springs and Struts: Set up the springs and struts for each element to indicate how you’d like the element to resize as the frame’s bounds change.

Go up to the top toolbar and select the Frame tool. Draw a frame around the green bar, as shown below:

PaintCode dynamic frame

Rename the frame to ActiveProgressFrame and group it with the ProgressTrackActive round rect (select both and click Group in the toolbar). Rename this group to ProgressActiveGroup.

Select ActiveProgressFrame again and check the box that says Apply only to entirely enclosed shapes. This will make the frame only affect the ProgressTrackActive bar, because ProgressTrackActive is the only element within the group that is inside the frame’s boundaries.

In order to keep the active bar centered horizontally, the active bar must retain the same height but be resizable horizontally.

To do this, select ProgressTrackActive and click on the right and bottom springs to make those dimensions static. Click on the center horizontal straight bar to change it to a spring, as shown in the screenshot below:

Progress Track Resizing

Test the settings you modified by selecting the ActiveProgressFrame and dragging the right transform handles to the right. The green bar should stretch as your resize the frame. If you drag a corner transform handle up or down, note that the green bar does not stretch vertically, but rather hugs the border of the frame.

Now you need a frame around the whole progress bar, so that you can make the entire progress bar resize as well as its frame changes. First, put everything in a group by selecting all elements (including the group you created already) and click Group in the toolbar. Name the new group Progress Bar. Your new group should look like the image below:

Progress bar group

Select the Frame tool and drag a frame around the whole progress bar. Move the frame into the Progress Bar group and name your new frame ProgressIndicatorFrame. Check the box next to Apply only to entirely closed shapes. If you forgot why you’re doing this, check the three steps listed earlier in this section.

Select ProgressActiveGroup. As before, you want this frame to resize horizontally, but not vertically, in order to preserve the centering of the control’s elements.

Click the horizontal bar in the springs/struts settings to turn it into a spring. You also want the progress track to remain locked to the left, top, and right edges of the frame, so ensure that only the bottom bar is a spring. The image below illustrates what your frame resizing settings should look like:

Progress active group sizing

Modify the resizing frame elements of ProgressTrack and Border shapes in the same manner.

Now it’s time to test it out! Select the ProgressIndicatorFrame and drag the transform handles around — your progress bar will resize horizontally, but your vertical dimension remains the same.

Select the ProgressTrackActive shape and click on the variable width button; it’s the small one with two square brackets and dot, immediately to the right of the Width attribute.

Progress track active rect width

Save your changes so you don’t lose any of your precious work. You’re now ready to hook up your control in your app.

Adding The Progress Control to Your App

In Xcode, open either the starter project you downloaded above, or your finished project from Part 1 of this series. Once the project is open, expand the Classes > Views group.

Your task is to create a subclass for the progress bar just as you did in Part 1 for the button. This time, however, you will subclass UIProgressView so the progress bar you designed in PaintCode will work just like the standard iOS progress view control.

Right-click the Views group and select New\File…. Select the iOS\Cocoa Touch\Objective-C class template. Name the class ProgressView, and make it a subclass of UIProgressView.

Open ProgressView.m, delete the code block for initWithFrame: and uncomment drawRect:. Here’s what the file should look like at this point:

#import "ProgressView.h"

@implementation ProgressView

- (void)drawRect:(CGRect)rect
    // Drawing code


Okay — your project is now ready for the code that PaintCode created for you.

Go back to PaintCode and make sure the code view is visible at the bottom; if not, select View > Code from the menu. Set the platform to iOS > Objective-C, the OS version to iOS 5+, origin to Default Origin, and memory management to ARC, as shown in the image below:

PaintCode code settings

Now copy and paste all of the code from PaintCode’s code view into drawRect:. This code is long, but don’t worry it’s all been pregenerated for you!

    //// General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();

//// Color Declarations
UIColor* fillColor = [UIColor colorWithRed: 0.416 green: 0.416 blue: 0.416 alpha: 1];
UIColor* strokeColor = [UIColor colorWithRed: 0.322 green: 0.322 blue: 0.322 alpha: 1];
UIColor* shadowColor2 = [UIColor colorWithRed: 0.2 green: 0.2 blue: 0.2 alpha: 1];
UIColor* shadowColor3 = [UIColor colorWithRed: 0.671 green: 0.671 blue: 0.671 alpha: 1];
UIColor* fillColor2 = [UIColor colorWithRed: 0.247 green: 0.247 blue: 0.247 alpha: 1];
UIColor* strokeColor2 = [UIColor colorWithRed: 0.188 green: 0.188 blue: 0.188 alpha: 1];
UIColor* color = [UIColor colorWithRed: 0 green: 0.886 blue: 0 alpha: 1];

//// Gradient Declarations
NSArray* outerRectGradientColors = [NSArray arrayWithObjects: 
    (id)fillColor.CGColor, nil];
CGFloat outerRectGradientLocations[] = {0, 1};
CGGradientRef outerRectGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)outerRectGradientColors, outerRectGradientLocations);
NSArray* gradientColors = [NSArray arrayWithObjects: 
    (id)fillColor2.CGColor, nil];
CGFloat gradientLocations[] = {0, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradientColors, gradientLocations);

//// Shadow Declarations
UIColor* darkShadow = shadowColor2;
CGSize darkShadowOffset = CGSizeMake(3.1, 3.1);
CGFloat darkShadowBlurRadius = 5;
UIColor* lightShadow = shadowColor3;
CGSize lightShadowOffset = CGSizeMake(3.1, 3.1);
CGFloat lightShadowBlurRadius = 5;

//// Frames
CGRect progressIndicatorFrame = CGRectMake(-1, 0, 321, 47);

//// Subframes
CGRect group = CGRectMake(CGRectGetMinX(progressIndicatorFrame) + 10, CGRectGetMinY(progressIndicatorFrame) + 9, CGRectGetWidth(progressIndicatorFrame) - 25, 20);
CGRect activeProgressFrame = CGRectMake(CGRectGetMinX(group) + floor(CGRectGetWidth(group) * 0.00000 + 0.5), CGRectGetMinY(group) + floor(CGRectGetHeight(group) * 0.00000 + 0.5), floor(CGRectGetWidth(group) * 1.00000 + 0.5) - floor(CGRectGetWidth(group) * 0.00000 + 0.5), floor(CGRectGetHeight(group) * 1.00000 + 0.5) - floor(CGRectGetHeight(group) * 0.00000 + 0.5));

//// Abstracted Attributes
CGRect progressTrackActiveRect = CGRectMake(CGRectGetMinX(activeProgressFrame) + 4, CGRectGetMinY(activeProgressFrame) + 5, CGRectGetWidth(activeProgressFrame) - 8, 10);

//// Progress Bar
    //// Border Drawing
    CGRect borderRect = CGRectMake(CGRectGetMinX(progressIndicatorFrame) + 2, CGRectGetMinY(progressIndicatorFrame) + 3, CGRectGetWidth(progressIndicatorFrame) - 5, 34);
    UIBezierPath* borderPath = [UIBezierPath bezierPathWithRoundedRect: borderRect cornerRadius: 4];
    CGContextSetShadowWithColor(context, darkShadowOffset, darkShadowBlurRadius, darkShadow.CGColor);
    CGContextBeginTransparencyLayer(context, NULL);
    [borderPath addClip];
    CGContextDrawLinearGradient(context, outerRectGradient,
        CGPointMake(CGRectGetMidX(borderRect), CGRectGetMinY(borderRect)),
        CGPointMake(CGRectGetMidX(borderRect), CGRectGetMaxY(borderRect)),

    ////// Border Inner Shadow
    CGRect borderBorderRect = CGRectInset([borderPath bounds], -lightShadowBlurRadius, -lightShadowBlurRadius);
    borderBorderRect = CGRectOffset(borderBorderRect, -lightShadowOffset.width, -lightShadowOffset.height);
    borderBorderRect = CGRectInset(CGRectUnion(borderBorderRect, [borderPath bounds]), -1, -1);

    UIBezierPath* borderNegativePath = [UIBezierPath bezierPathWithRect: borderBorderRect];
    [borderNegativePath appendPath: borderPath];
    borderNegativePath.usesEvenOddFillRule = YES;

        CGFloat xOffset = lightShadowOffset.width + round(borderBorderRect.size.width);
        CGFloat yOffset = lightShadowOffset.height;
            CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),

        [borderPath addClip];
        CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(borderBorderRect.size.width), 0);
        [borderNegativePath applyTransform: transform];
        [[UIColor grayColor] setFill];
        [borderNegativePath fill];


    //// ProgressTrack Drawing
    CGRect progressTrackRect = CGRectMake(CGRectGetMinX(progressIndicatorFrame) + 12, CGRectGetMinY(progressIndicatorFrame) + 12, CGRectGetWidth(progressIndicatorFrame) - 29, 14);
    UIBezierPath* progressTrackPath = [UIBezierPath bezierPathWithRoundedRect: progressTrackRect cornerRadius: 7];
    CGContextSetShadowWithColor(context, lightShadowOffset, lightShadowBlurRadius, lightShadow.CGColor);
    CGContextBeginTransparencyLayer(context, NULL);
    [progressTrackPath addClip];
    CGContextDrawLinearGradient(context, gradient,
        CGPointMake(CGRectGetMidX(progressTrackRect), CGRectGetMinY(progressTrackRect)),
        CGPointMake(CGRectGetMidX(progressTrackRect), CGRectGetMaxY(progressTrackRect)),

    ////// ProgressTrack Inner Shadow
    CGRect progressTrackBorderRect = CGRectInset([progressTrackPath bounds], -darkShadowBlurRadius, -darkShadowBlurRadius);
    progressTrackBorderRect = CGRectOffset(progressTrackBorderRect, -darkShadowOffset.width, -darkShadowOffset.height);
    progressTrackBorderRect = CGRectInset(CGRectUnion(progressTrackBorderRect, [progressTrackPath bounds]), -1, -1);

    UIBezierPath* progressTrackNegativePath = [UIBezierPath bezierPathWithRect: progressTrackBorderRect];
    [progressTrackNegativePath appendPath: progressTrackPath];
    progressTrackNegativePath.usesEvenOddFillRule = YES;

        CGFloat xOffset = darkShadowOffset.width + round(progressTrackBorderRect.size.width);
        CGFloat yOffset = darkShadowOffset.height;
            CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),

        [progressTrackPath addClip];
        CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(progressTrackBorderRect.size.width), 0);
        [progressTrackNegativePath applyTransform: transform];
        [[UIColor grayColor] setFill];
        [progressTrackNegativePath fill];


    //// Group
        //// ProgressTrackActive Drawing
        UIBezierPath* progressTrackActivePath = [UIBezierPath bezierPathWithRoundedRect: progressTrackActiveRect cornerRadius: 5];
        [color setFill];
        [progressTrackActivePath fill];

//// Cleanup

Note: Your code might be slightly different than this due to how you drew things in PaintCode, but should still be OK.

You can easily see how much work PaintCode does for you. Could you imagine coding all that by hand? If you’re a fan of pixel-pushing, go for it — but most of us like to save time and get on to coding more interesting things! :]

At the top of the method are declarations for colors, gradients, and shadows. The frame declarations follow on next. Make note of the progressTrackActiveRect variable in the //// Abstracted Attributes section. This will be used later on to control the progress of the green bar via the progress property, similar to how a regular UIProgressView works.

Next there’s the long Progress Bar section of code that’s in charge of drawing the border, progress track and active rect, and after that another section to manage the ProgressActiveGroup. At the very end of the code block you’ll find a few cleanup methods.

Note: This tutorial won’t go into depth on the Core Graphics used to draw the controls. The main reason for using PaintCode is to design some really amazing UI controls without mucking about with Core Graphics libraries. This frees you up to spend time on developing and testing other areas of your app, instead of spending it on pixel pushing.

However, a little bit of Core Graphics knowledge will go a long way if you want to understand the code that PaintCode generates for you so you can tweak it. To learn more about Core Graphics, check out our epic 7-part Core Graphics tutorial series!

Drawing the Progress View

Now that you have your progress bar in place, the first test is to make sure it’s being drawn correctly.

Locate Resources\Storyboards\MainStoryboard.storyboard in the Xcode project and open it. Go to the Progress View Controller scene and add a View object from the Object Library. Change the class to ProgressView in the Identity Inspector and give it the following attributes in the Size Inspector:

  • X: 20
  • Y: 183
  • Width: 280
  • Height: 46

Progress view size inspector

Build and run your project and switch to the Progress tab of your app. Your control should be displayed as in the following screenshot:

Progress bar first run

Your custom progress bar looks pretty good — but it’s not being drawn to the size of the view placed in the storyboard. To fix this, go back to drawRect: in ProgressView.m and find the following line of code:


    //// Frames
    CGRect progressIndicatorFrame = CGRectMake(2, 1, 318, 34);


Update this line of code as shown below:

- (void)drawRect:(CGRect)rect

    // Frames
    CGRect progressIndicatorFrame = rect;


This ensures that the frame’s size is set based on the view’s rectangle.

Build and run your project, and again switch to the Progress tab. Your progress bar should now be drawn to match the view you placed in the storyboard, as demonstrated below:

Progress bar frame fixed

The progress bar is now being drawn correctly — it’s time to make the progress bar animate just as it would in a real-world app.

Adding Dynamic Elements to the Progress View

Open up ProgressViewController.m and add the following code just before the @implementation line:

// 1
#import "ProgressView.h"
#import "ProgressViewController.h"

// 2
#define kSecondsForCompleteUpdate   3.0
#define kUpdateInterval             0.02

// 3
@interface ProgressViewController ()

@property (weak, nonatomic) IBOutlet ProgressView *progressView;
@property (weak, nonatomic) IBOutlet UIButton *startProgressButton;
@property (strong, nonatomic) NSTimer *timer;


Here’s a short review of what the above code does:

  1. Imports ProgressView.h so it can be leveraged in the view controller.
  2. Defines two variables kSecondsForCompleteUpdate and kUpdateInterval and assigns them a value. These two variables will help simulate a progress activity to which your control will respond.
  3. Adds a class extension with outlets for UIButton and UIProgressView, and adds a timer variable to control the update of the progress view’s progress property.

Now add the following code to (still in ProgressViewController.m):

-(IBAction)startProgressTapped {
    self.progressView.progress = 0.0;
    self.startProgressButton.enabled = NO;
    self.timer = [NSTimer timerWithTimeInterval:kUpdateInterval
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    [self.timer fire];

This code will be called when a button is tapped on the screen. It first sets the progress property of the progress view to 0. It then disables the button so that it can’t be pressed again while the progress animation is running. Next, it creates a timer to control the length of the simulated event.

Finally, the code adds the timer to the current run loop and starts the timer to simulate the start of the event.

Take a look at the self.timer line. You can see that the timer calls updateProgressView, which is the simulator that will update your progress bar. You’ll add this now.

Add the following code to ProgressViewController.m:

-(void)updateProgressView; {
    if (self.progressView.progress < 1.0) {
        self.progressView.progress += (kUpdateInterval / kSecondsForCompleteUpdate);
    } else {
        [self.timer invalidate];
        self.startProgressButton.enabled = YES;

This method checks whether the current progress is less than 1.0, which means the task is not yet 100% complete. If so, it increments the progress bar gradually, using the variables you set earlier to control the rate at which the progress bar increments.

If the progress is equal to or greater than 1.0, that means the task is 100% complete, and the method invalidates the timer and re-enables the button so that you can run the simulation again.

All that's left to add is the button to the screen that will trigger startProgressTapped to kick off the whole sequence of events.

Adding The Start Progress Button

Switch to the storyboard and drag a Round Rect Button object into the Progress View Controller scene. In the Attributes Inspector change the title to "Start Progress" and connect the Touch Up Inside event to the startProgressTapped selector.

Next, modify the button's attributes in the Size Inspector as shown below:

  • X: 97
  • Y: 20
  • Width: 128
  • Height: 44
  • Autosizing: Fixed to left, top, right margin and fixed width. Flexible to the bottom margin and flexible width.

Button size attributes

Next, connect the outlets for the button and progress view. Your resulting scene should look like the following:

Progress controller connections

Build and run your app, change to the Progress tab, and click the Start Progress button. Your progress bar should be drawn as shown below:

Testing progress with button

Umm...the progress bar seems to be stuck. Nothing happens when you tap the button. What gives?

Updating The Progress Track

The issue is that there's no code in ProgressView to modify the actual progress track. It's still being drawn statically, just as it was drawn in PaintCode. You need to add some code to draw the track based on the progress property of the progress view.

Switch to ProgressView.m. Locate the following section of code:


    //// Abstracted Attributes
    CGRect progressTrackActiveRect = CGRectMake(CGRectGetMinX(activeProgressFrame) + 2, CGRectGetMinY(activeProgressFrame) + 2, CGRectGetWidth(activeProgressFrame) - 4, 10);


Replace the entire CGRect progressTrackActiveRect line with the following code:

CGRect progressTrackActiveRect = CGRectMake(CGRectGetMinX(activeProgressFrame) + 3,
                                                CGRectGetMinY(activeProgressFrame) + 2,
                                                (CGRectGetWidth(activeProgressFrame) - 4) * self.progress,

The new code calculates the width of the progress indicator rectangle's width based on the value of the progress property.

There's one final thing to do — set the initial value of the progress property to zero. That way, when the app starts, the progress bar will be reset to zero, as a user would expect.

Switch to ProgressViewController.m and add the following code:

-(void)viewDidLoad {
    [super viewDidLoad];
    self.progressView.progress = 0.0;

Build and run again, move to the Progress tab and tap the button. Your progress bar should start at zero and start to fill the track gradually, as in the screenshot below:

Final project

You now have a complete progress view that updates dynamically! The progress bar will update over the span of 3 seconds to emulate some background process or download activity.

Where To Go From Here

You can download the final project with the PaintCode file and Xcode project here.

Congratulations - you've made a custom look for a progress bar in PaintCode and integrated it into an app, and learned a lot in the process!

You can use these newfound skills to customize the look of the progress bar however you'd like - even in the brand new "flat" style of iOS 7 announced in the keynote yesterday :]

And good news - there's still more! In the third and final part of this series, I'll show you how to implement dynamic bezier arrows to create a fun quiz game.

I hope you've enjoyed this tutorial, and if you have questions or comments or want to show off your creations, please join the forum discussion below!




More like this