Development Tools – Opacity

Assumptions:

  1. It isn’t required, but it will be helpful if you are familiar with layer based image editing or have experience with a graphics editing program such as Adobe Photo Shop

Getting Started

A important aspect of developing apps for iOS or OS X is creating a great User Interface. Even if your app has all the latest and greatest functionality, you are going to have a hard time getting anybody to use it if it is lacking in the UI department,.

Opacity to the rescue!

Opacity is a layer based vector graphics application for the Mac that makes designing graphics for your app a breeze. Opacity has built in templates for skinning buttons, creating 1x and 2x resolutions non-retina & retina devices, and many other time saving features as you will see!

Step 1: Download Opacity

Currently (as of this writing) Opacity is not available through the Mac App Store and I’m not sure if the developer has any plans to add it any time soon, however it can be downloaded at http://likethought.com/opacity/. I recommend starting with the trial version (free), but the full version will be necessary to remove a pesky watermark from your exported images (currently the full version is $89.99).

Additionally, there is a great video tutorial on the home page that I would recommend watching before moving on to Step 2.

Step 2: Creating a UIButton Image

Lets get started!

Open Opacity, then choose “iOS” from the left pane, and then select the “Button” template and click “Choose”

Opacity will then create a new Opacity project file based on the default button template, which is perfectly tailored for iOS applications.

At first glance you may think, “Wow that’s kind of a skinny button, right?”

 Well, not at all!

The Opacity template is setup to create a resizable image for all your button needs (All of you newbies who haven’t yet figured out how create a resizable asset in UIKit yet, don’t worry it will be covered in another posting soon!).

Notice, in the lower lefthand corner of the window shown, there is a control with the title “2x” (it may be “1x” on your system), select it to reveal all the current resolutions this file is setup to build (“1x” and “2x”). Because some iOS devices have a retina screen and other are just standard, when creating graphics for your iOS application you will need to have both 1x and 2x (otherwise your graphics will be blurry on a retina device).

So lets look at the file inspector to see how to edit our new button asset. Select “Inspector” from the toolbar or press CMD+I.

The Inspector has 4 sections:

Vectors – This is where all of your vector’s for the currently selected layer can be edited.

Layers – This is where you can edit properties of the currently selected layer

Image – This has all the global properties of the image

Factories – This is where you can setup different ways to build your image

Many of the features in these inspectors are advanced and I won’t be able to cover them within the scope of this article, however, for the most part they are very intuitive.

Step 3: Change the button image color

First, ensure the left layers pane is visible in the main editor as shown above, if it is not showing, press the layers button located in the lower left corner or press CMD+L. Then select the layer named “Button Color, ” this is the layer that contains a rounded rectangle which is filled with the color red (The little eyeball graphic next to each layer determines if that layer is currently visible).

Next click on the button image in the red area, then

In the Inspector, select the “Vectors” tab, your inspector should now resemble the one below

Next Click on the red box to change the color to blue (I used the Aqua crayon color).

 

 

Step 4: Build Your Images

Now that we have our blue button asset defined, it’s time to export the 1x and 2x .png representations so we can add them to our project.

In the inspector, select the “Factories” tab.

The Opacity Button template was nice enough to setup both a 1x and 2x factory for us, so all the is left to do is click “Build All.” However, if you want to change the location at which these files will be created, you need to either save your project in that location or uncheck “Relative Path” for each factory and then manually change each path.

And you have it! A perfect button asset in both 1x and 2x, ready for your app!

  

Other Features of Opacity

So you now know how to create static graphics, but Opacity is much more capable than that! In addition to just creating graphics, Opacity has the ability to generate the graphics code used to create the graphics. This functionality is just plain awesome!

For example lets say you need to display a pie chart with dynamic data in your app. You could easily generate a generic pie chart in Opacity, have it then export the graphics code, then add properties for the drawing parameters that are dynamic!

Just as an example, I’ll show you how to have Opacity export a custom UIView subclass that implements drawRect: in order to draw our button.

Disclaimer: In practice, DO NOT create custom buttons this way, it is much more efficient to use a resizable asset for skinning your UIButtons!

Back in our Factories inspector, click the “+” button to create a new factory. Your new factory will be added below you existing factories. Select the “Format” control and change the value to “Source Code” as shown below

Your new factory will then have a few new properties (Language, Class, Superclass) as shown below. Setting them as shown below will create both an image.m and image.h file. image.m will be the implementation for a UIView subclass named “MyView” which implements drawRect: to draw our blue button image.

Build it, then open image.m in Xcode. Look at all that nasty Core Graphics code that you didn’t have to write!

//
// image.m
// New Button
//
// Created by NSCookBook on 12/2/12
// This code was generated by Opacity. You may use or modify it in any way.
//

#import "image.h"

const CGFloat kMyViewWidth = 33.0f;
const CGFloat kMyViewHeight = 44.0f;

@implementation MyView

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setOpaque:NO];
}
return self;
}

- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self setOpaque:NO];
}
return self;
}

- (CGSize)sizeThatFits:(CGSize)size
{
return CGSizeMake(kMyViewWidth, kMyViewHeight);
}

- (void)drawRect:(CGRect)dirtyRect
{
CGRect imageBounds = CGRectMake(0.0f, 0.0f, kMyViewWidth, kMyViewHeight);
CGRect bounds = [self bounds];
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *color;
CGFloat resolution;
CGFloat alignStroke;
CGFloat stroke;
CGMutablePathRef path;
CGPoint point;
CGPoint controlPoint1;
CGPoint controlPoint2;
CGGradientRef gradient;
NSMutableArray *colors;
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGPoint point2;
CGFloat locations[4];
resolution = 0.5f * (bounds.size.width / imageBounds.size.width + bounds.size.height / imageBounds.size.height);

CGContextSaveGState(context);
CGContextTranslateCTM(context, bounds.origin.x, bounds.origin.y);
CGContextScaleCTM(context, (bounds.size.width / imageBounds.size.width), (bounds.size.height / imageBounds.size.height));

// Setup for Shadow Effect
color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.6f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f * resolution, 1.0f * resolution), 0.0f * resolution, [color CGColor]);
CGContextBeginTransparencyLayer(context, NULL);

// Button Color

stroke = 1.0f;
stroke *= resolution;
if (stroke < 1.0f) {
stroke = ceilf(stroke);
} else {
stroke = roundf(stroke);
}
stroke /= resolution;
alignStroke = fmodf(0.5f * stroke * resolution, 1.0f);
path = CGPathCreateMutable();
point = CGPointMake(24.5f, 42.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathMoveToPoint(path, NULL, point.x, point.y);
point = CGPointMake(32.5f, 34.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(28.889f, 42.5f);
controlPoint2 = CGPointMake(32.5f, 38.889f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(32.5f, 8.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
point = CGPointMake(24.5f, 0.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(32.5f, 4.111f);
controlPoint2 = CGPointMake(28.889f, 0.5f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(8.5f, 0.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
point = CGPointMake(0.5f, 8.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(4.111f, 0.5f);
controlPoint2 = CGPointMake(0.5f, 4.111f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(0.5f, 34.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
point = CGPointMake(8.5f, 42.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(0.5f, 38.889f);
controlPoint2 = CGPointMake(4.111f, 42.5f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(24.5f, 42.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
CGPathCloseSubpath(path);
color = [UIColor colorWithRed:0.0f green:0.502f blue:1.0f alpha:1.0f];
[color setFill];
CGContextAddPath(context, path);
CGContextFillPath(context);
color = [UIColor colorWithRed:0.1f green:0.1f blue:0.1f alpha:0.0f];
[color setStroke];
CGContextSetLineWidth(context, stroke);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);

// Button Shine

stroke = 1.0f;
stroke *= resolution;
if (stroke < 1.0f) {
stroke = ceilf(stroke);
} else {
stroke = roundf(stroke);
}
stroke /= resolution;
alignStroke = fmodf(0.5f * stroke * resolution, 1.0f);
path = CGPathCreateMutable();
point = CGPointMake(24.5f, 42.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathMoveToPoint(path, NULL, point.x, point.y);
point = CGPointMake(32.5f, 34.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(28.889f, 42.5f);
controlPoint2 = CGPointMake(32.5f, 38.889f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(32.5f, 8.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
point = CGPointMake(24.5f, 0.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(32.5f, 4.111f);
controlPoint2 = CGPointMake(28.889f, 0.5f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(8.5f, 0.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
point = CGPointMake(0.5f, 8.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(4.111f, 0.5f);
controlPoint2 = CGPointMake(0.5f, 4.111f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(0.5f, 34.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
point = CGPointMake(8.5f, 42.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
controlPoint1 = CGPointMake(0.5f, 38.889f);
controlPoint2 = CGPointMake(4.111f, 42.5f);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point.x, point.y);
point = CGPointMake(24.5f, 42.5f);
point.x = (roundf(resolution * point.x + alignStroke) - alignStroke) / resolution;
point.y = (roundf(resolution * point.y + alignStroke) - alignStroke) / resolution;
CGPathAddLineToPoint(path, NULL, point.x, point.y);
CGPathCloseSubpath(path);
colors = [NSMutableArray arrayWithCapacity:4];
color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.2f];
[colors addObject:(id)[color CGColor]];
locations[0] = 1.0f;
color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f];
[colors addObject:(id)[color CGColor]];
locations[1] = 0.75f;
color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.1f];
[colors addObject:(id)[color CGColor]];
locations[2] = 0.5f;
color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f];
[colors addObject:(id)[color CGColor]];
locations[3] = 0.5f;
color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.75f];
[colors addObject:(id)[color CGColor]];
locations[4] = 0.02f;
color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.6f];
[colors addObject:(id)[color CGColor]];
locations[5] = 0.02f;
gradient = CGGradientCreateWithColors(space, (CFArrayRef)colors, locations);
CGContextAddPath(context, path);
CGContextSaveGState(context);
CGContextEOClip(context);
point = CGPointMake(16.5f, 1.0f);
point2 = CGPointMake(16.5f, 42.0f);
CGContextDrawLinearGradient(context, gradient, point, point2, (kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation));
CGContextRestoreGState(context);
CGGradientRelease(gradient);
color = [UIColor colorWithRed:0.1f green:0.1f blue:0.1f alpha:0.6f];
[color setStroke];
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);

// Shadow Effect
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);

CGContextRestoreGState(context);
CGColorSpaceRelease(space);
}

@end

Now I must confess, that code is far from “shippable” and in some cases won’t build under ARC (don’t worry Xcode will help you fix it), but it can be very helpful when trying to figure something out in Core Graphics.

Conclusion

Creating graphic for your iOS or OS X application is made really simple with Opacity.

How Simple?

So simple a developer can do it!

Trackbacks

  1. […] resizable graphics that were generated using Opacity which was the topic of another of our posts, Developer Tools – Opacity. If you are following along you can download both the 1x and 2x graphics […]

Speak Your Mind

*

css.php
Privacy Policy