PaintCode Tutorial: Dynamic Buttons

Learn how to make beautiful resizable and recolorable buttons, using a popular tool called PaintCode that automatically creates Core Graphics code for you as you draw! By Felipe Laso-Marsetti.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Adding a Button Frame

Select Frame from the top toolbar and draw a frame around the button shape. Set the frame properties as shown below:

Adding a frame for the button

Multi-select the Frame and the Rounded Rectangle shapes, and group them using the Option-Command-G key combination. Alternately, you can use the Selection > Group menu item to perform the same task. Rename the group to Button.

Note that you can now see the grouped shape and its two sub-shapes in the top section of the left hand pane, as shown below:

Grouping elements

Select the Rounded Rectangle in the left panel. Look at the icon showing the item constraints – it’s the box within a frame, with straight bars and springs next to the shape placement and dimension values.

ResizeWindow

You’ll want your button to be resizable horizontally and vertically, while remaining centered horizontally in the frame. This means the top, left, and right constraints connecting the frame and the box should be straight (think rigid), and the bottom should be a spring (think flexible). This will keep the button centered in the frame.

However, inside the box, the vertical and the horizontal constraints should be springs. This allows the button to stretch to fill the frame.

To modify the constraints of your object, simply click the straight rod or the spring representing the constraint you wish to change to alternate it between a straight rod and a spring.

When you have finished modifying the constraints, they should look like the image below:

Button constraints

All done? Great! Now you can select the Frame surrounding your button, and drag its transform handles around the screen to see the button resize, just as in the screenshot below:

Final button

That completes the creation of your button in PaintCode — now you’re ready to hook it up with some code.

Bringing Your Button Into Your iOS App

You now have a complete button to use in your project — all without writing a stitch of code! But don’t worry, code fiends, it’s time to roll up your sleeves and use this new button in your project.

There’s a starter project all ready for you that you can use to showcase your new button.
Download the PaintCode Starter Project, extract it to a suitable location on your hard drive, open it in Xcode, and take a look at what’s already in place.

Note: The storyboard for the project has Auto Layout turned off. It’s not a requirement for all new projects, but in this case the elements work better with the good old springs and struts.

Note: The storyboard for the project has Auto Layout turned off. It’s not a requirement for all new projects, but in this case the elements work better with the good old springs and struts.

The project is a tab bar application with three view controllers; one for each PaintCode element you’ll create in each part of this series. In this PaintCode tutorial you will use the ButtonViewController class to host your new dynamic button.

There’s also an empty folder within Classes > Views. In this series, this is where you are going to be creating your custom controls. Rather than creating a control from scratch, you will be subclassing existing controls and replacing the draw code.

In this tutorial, you will be subclassing UIButton. Let’s get started!

Creating Your Custom Button

Inside the Classes folder, right-click on the Views folder. Select New\File… and create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class ButtonView, and make it a subclass of UIButton.

This class will draw the custom button using the code created by PaintCode.

To get your view all ready for your custom button, open ButtonView.m, delete the initWithFrame: method and uncomment the code for drawRect:. The file should now look like this:

#import "ButtonView.h"

@implementation ButtonView

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

@end

drawRect: is where the magic happens – this is where you will be putting the code that was generated by PaintCode. If you are not familiar with drawRect: and what it does, refer back to our Core Graphics tutorial series.

Switch back to PaintCode and make sure the Code View is visible. If it’s not visible, use the View > Code menu option to make it visible. Find the option settings along the bottom edge of the main PaintCode view, as shown in the image below:

PaintCode code settings

Set the platform to iOS > Objective-C, the OS version to iOS 5+, origin to Default Origin, and memory management to ARC. If this is your first time using PaintCode — or you haven’t mucked about with these settings before — everything except for memory management should already be set correctly by default.

Copy and paste the code from PaintCode’s code view into drawRect:. The method should now look like the following:

- (void)drawRect:(CGRect)rect
{
    //// General Declarations
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //// Color Declarations
    UIColor* buttonColorDark = [UIColor colorWithRed: 0.439 green: 0.004 blue: 0 alpha: 1];
    UIColor* buttonColorLight = [UIColor colorWithRed: 1 green: 0 blue: 0 alpha: 1];
    UIColor* innerGlowColor = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.502];
    UIColor* shadowColor2 = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.51];
    
    //// Gradient Declarations
    NSArray* buttonGradientColors = [NSArray arrayWithObjects:
                                     (id)buttonColorLight.CGColor,
                                     (id)buttonColorDark.CGColor, nil];
    CGFloat buttonGradientLocations[] = {0, 1};
    CGGradientRef buttonGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)buttonGradientColors, buttonGradientLocations);
    
    //// Shadow Declarations
    UIColor* outerGlow = innerGlowColor;
    CGSize outerGlowOffset = CGSizeMake(0.1, -0.1);
    CGFloat outerGlowBlurRadius = 3;
    UIColor* highlight = shadowColor2;
    CGSize highlightOffset = CGSizeMake(0.1, 2.1);
    CGFloat highlightBlurRadius = 2;
    
    //// Frames
    CGRect frame = CGRectMake(0, 0, 480, 49);
    
    //// Button
    {
        //// Rounded Rectangle Drawing
        CGRect roundedRectangleRect = CGRectMake(CGRectGetMinX(frame) + 4, CGRectGetMinY(frame) + 4, CGRectGetWidth(frame) - 7, 41);
        UIBezierPath* roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: roundedRectangleRect cornerRadius: 6];
        CGContextSaveGState(context);
        CGContextSetShadowWithColor(context, outerGlowOffset, outerGlowBlurRadius, outerGlow.CGColor);
        CGContextBeginTransparencyLayer(context, NULL);
        [roundedRectanglePath addClip];
        CGContextDrawLinearGradient(context, buttonGradient,
                                    CGPointMake(CGRectGetMidX(roundedRectangleRect), CGRectGetMinY(roundedRectangleRect)),
                                    CGPointMake(CGRectGetMidX(roundedRectangleRect), CGRectGetMaxY(roundedRectangleRect)),
                                    0);
        CGContextEndTransparencyLayer(context);
        
        ////// Rounded Rectangle Inner Shadow
        CGRect roundedRectangleBorderRect = CGRectInset([roundedRectanglePath bounds], -highlightBlurRadius, -highlightBlurRadius);
        roundedRectangleBorderRect = CGRectOffset(roundedRectangleBorderRect, -highlightOffset.width, -highlightOffset.height);
        roundedRectangleBorderRect = CGRectInset(CGRectUnion(roundedRectangleBorderRect, [roundedRectanglePath bounds]), -1, -1);
        
        UIBezierPath* roundedRectangleNegativePath = [UIBezierPath bezierPathWithRect: roundedRectangleBorderRect];
        [roundedRectangleNegativePath appendPath: roundedRectanglePath];
        roundedRectangleNegativePath.usesEvenOddFillRule = YES;
        
        CGContextSaveGState(context);
        {
            CGFloat xOffset = highlightOffset.width + round(roundedRectangleBorderRect.size.width);
            CGFloat yOffset = highlightOffset.height;
            CGContextSetShadowWithColor(context,
                                        CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
                                        highlightBlurRadius,
                                        highlight.CGColor);
            
            [roundedRectanglePath addClip];
            CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(roundedRectangleBorderRect.size.width), 0);
            [roundedRectangleNegativePath applyTransform: transform];
            [[UIColor grayColor] setFill];
            [roundedRectangleNegativePath fill];
        }
        CGContextRestoreGState(context);
        
        CGContextRestoreGState(context);
        
        [[UIColor blackColor] setStroke];
        roundedRectanglePath.lineWidth = 2;
        [roundedRectanglePath stroke];
    }
    
    //// Cleanup
    CGGradientRelease(buttonGradient);
    CGColorSpaceRelease(colorSpace);    
}

If you read through the code, you will see that it’s using Core Graphics to do the drawing. Although some (or most) of the methods may be new to you, they aren’t really that difficult to understand. Besides, this is what our friend Mr. PaintCode is here for; to avoid having to learn Core Graphics and spend sleepless nights struggling with custom drawing code! :]

Note: Don’t worry about the code, spacing or syntax in drawRect: for now. You’ll come back to this code shortly and make some edits to this method, including updating the syntax to modern Objective-C.

Note: Don’t worry about the code, spacing or syntax in drawRect: for now. You’ll come back to this code shortly and make some edits to this method, including updating the syntax to modern Objective-C.

Now it’s time to test the button.

Locate the storyboard in your Project Navigator and go to the ButtonViewController scene. Drag a View object from the Object Library and set its X and Y coordinates to 20 and 35. Next, set its Width and Height to 280 and 41.

Finally, update the view’s Autosizing widget so it only allows the button to resize horizontally, as shown below:

Button view autosizing

Switch to the Identity Inspector and set the class to ButtonView.

Note:You might be wondering why you used a UIView in the storyboard, when ButtonView is a subclass of UIButton. The reason is that UIButton is a subclass of UIView and thus a button is a view.

Later on in this PaintCode tutorial you will make the button act and behave just like it should. After all — it is a UIButton subclass!

Note:You might be wondering why you used a UIView in the storyboard, when ButtonView is a subclass of UIButton. The reason is that UIButton is a subclass of UIView and thus a button is a view.

Later on in this PaintCode tutorial you will make the button act and behave just like it should. After all — it is a UIButton subclass!

Subclass watch out

Time to test things and see your new custom control in action!

Click Run and take a look at your shiny new button for the very first time:

First button test

Umm…the button looks kinda cut off. What’s going on?

Take a look at drawRect: in ButtonView.m:

- (void)drawRect:(CGRect)rect
{
    ...

    //// Frames
    CGRect frame = CGRectMake(0, 0, 480, 49);
    
    ...
}

Aha! The button’s frame is being calculated with fixed values for width and height. What you want is to have dynamic width and height values — and respond accordingly to the frame you use to create the button.

To fix this situation this, simply modify the line which sets the frame variable as follows:

- (void)drawRect:(CGRect)rect
{
    ...

    //// Frames
    CGRect frame = rect;
    
    ...
}

The above code simply sets the view’s frame as the frame for drawing the button.

Run the project one more time and behold!

First button test fixed

Yay! The button is being drawn just as intended. Rotate your simulator (or your device) and notice how the button resizes horizontally but keeps its height, just as expected.

You’ve made great progress so far — but what if you made the button even more dynamic?