Introduction
The goal of this article is to show how easy it is to create a Watch app that uses CoreGraphics to draw a pie chart. I was writing an app that needed a sequence of simple images as animation frames. I find it extremely time consuming to generate many images manually and cumbersome to make minor image resizing changes. So, I did some research and found CoreGraphics as a solution.
The complete source code can be downloaded from github: https://github.com/yuetwong/PieChart.
The sample app looks like the following:
Getting Started
Start with a New Project in Xcode
Let's start by creating a new project In Xcode. Note that I am using WatchOS 2.0 and Xcode 7.0.1. To create a new project, navigate to File\New\Project.. and select the watchOS\Application\iOS App with WatchKit App template.
Enter the product name as PieChart and uncheck Include Notification Scene because we will not be using Notification Scene.
On the next screen, choose a directory to store the new project.
Designing the Interface
The new project should have the following structure:
Open the Interface.storyboard
under the PieChart WatchKit App folder. You should see an empty interface controller. Add the following interface objects to the empty interface controller: 2 labels and a picker.
Configure the objects by selecting the object in the storyboard and click the "Show the Attributes inspector" icon.
- Label
- Change the label text with this message :
Dial the digital crown to adjust the pie chart
- Change the text color to yellow
- Change the font to System, size 12
- Change the line number to 2 to allow the text to span 3 lines
- Change the text color to orange
- Change the text alignment to "center"
- Change the label alignment to "center"
- Picker
- Change the picker style from List to Sequence
- Set the picker's horizontal alignment to "center"
- A label to display the pie chart percentage.
- Change the Text to 0%
- Set the text alignment to "center"
- Set the label's horizontal alignment to "center"
Coding
Alright, we have the interface, but it does not do anything right now. We need to write some code to bring it to life. In summary, we need:
- to create a method to draw pie chart images
- to setup picker with images
- to display the selected pie chart image
- to display the selected pie chart percentage
PieChart Class
Since we will be making many images as animation frames for the picker, we will create a method that can be called many times to create the pie charts of different sizes.
We will create a PieChart
class with a method to draw the pie chart. We will create the PieChart
class in the WatchKit Extension since it will be used by the Watch App. To create the class, go to the Project Navigator, right click on the PieChart WatchKit Extension and click New Files... on the popup menu.
Select the watchOS\Source\WatchKit Class template:
Create a PieChart
class by subclassing on NSObject
.
2 files are created: PieChart.h and PieChart.m. PieChart.h is the header file and PieChart.m is source file.
The PieChart
class needs to use CoreGraphics
. We need to import the UIKit.h file in order to use the CoreGraphics
library functions. Add the following line in the PieChart.h header file to import the UIKit.h file.
#import <UIKit/UIKit.h>
The PieChart
class will have one method, PieChartImageBySize
, which will create a pie chart image. The +
sign at the beginning of the method indicates that the method is a class method. You can call a class method directly without creating an instance of the class, unlike the instance method.
The PieChart.h file will look like below:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface PieChart : NSObject
+(UIImage * )PieChartImageBySize:(float)imageSize outlineWidth:(CGFloat)outlineWidth
pieColor:(UIColor *)pieColor startRatio:(CGFloat)startRatio
endRatio:(CGFloat)endRatio;
@end
Now, we are ready to write the method for creating a pie chart image. Open the PieChart.m file and add the code for PieChartImageBySize
between the lines: @implementation
and @end
.
The logic of the PieChartImageBySize
method can be broken down as follows:
- Constants for circle
fullCircle
= 2π, is the angle of a full circle. initialAngle
is used to adjust the startAngle
and endAngle
passed as input parameters to the CGContextAddArc
function, which measures angles in radian from the positive-x axis which is at the 3 o'clock position. Since we want the pie chart to start from the 12 o'clock position, we adjust the angles by 3π/2. centerPoint
is the center of the pie chart. The radius
of the pie chart is adjusted by outlineWidth
to avoid edges being cut off.
- Get graphics context
context
is the area to draw on. UIGraphicsBeginImageContextWithOptions
creates the context with the specified size. Setting the scale
parameter to 0.0 will scale the image according to the scale factor of the device's main screen. This will ensure the image appears sharp on different devices of different sizes.
- Draw the pie chart
- Before drawing the pie chart, we set the color by calling the function
CGContextSetFillColorWithColor
. Then, we call the function CGContextAddArc
to draw the arc of the pie chart. The CGContextAddLineToPoint
function draws a line from the end of the arc to the center of the pie chart. The function CGContextClosePath
closes the path by drawing a line from the center point to the starting point. The last function, CGContextFillPath
, paints the pie chart with the color set earlier.
- Draw the outline of the pie chart
- Drawing the outline is similar to drawing the pie chart. Instead of calling CGContextSetFillColorWithColor, we call
CGContextSetStrokeColorWithColor
to set the colour of the outline. Instead of calling CGContextFillPath
, we call CGContextStrokePath
to draw the outline.
- Return the image
- The last step is to call the function,
UIGraphicsGetImageFromCurrentImageContext
, to get the image and return to the caller. We also close the context using function, UIGraphicsEndImageContext
.
+(UIImage * )PieChartImageBySize:(float)imageSize outlineWidth:(CGFloat)outlineWidth
pieColor:(UIColor *)pieColor startRatio:(CGFloat)startRatio endRatio:(CGFloat)endRatio
{
CGFloat fullCircle = 2.0 * M_PI;
CGFloat initialAngle = 3.0 * M_PI_2;
CGFloat startAngle = startRatio * fullCircle + initialAngle;
CGFloat endAngle = endRatio * fullCircle + initialAngle;
CGPoint centerPoint = CGPointMake(imageSize/2, imageSize/2);
CGFloat radius = (imageSize/2) - outlineWidth;
CGSize contextSize = CGSizeMake(imageSize, imageSize);
bool opaque = YES;
CGFloat scale = 0.0;
UIGraphicsBeginImageContextWithOptions(contextSize, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, pieColor.CGColor);
CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, startAngle, endAngle, 0);
CGContextAddLineToPoint(context, centerPoint.x, centerPoint.y);
CGContextClosePath(context);
CGContextFillPath(context);
CGContextSetLineWidth(context, outlineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor darkGrayColor].CGColor);
CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, 0, fullCircle, 0);
CGContextStrokePath(context);
UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Now that we have a function that can create a pie chart image, we can code the next function which will create 101 pie chart images. Open the InterfaceController.m file in the PieChart WatchKit Extension folder and add the following line to import the PieChart.h header file.
#import "PieChart.h"
Then, add the loadImage
function. The function generates 101 pie chart images by calling the function PieChartImageBySize
. The start
ratio is 0.0 which means the pie chart starts from the 12 o'clock position. The end
ratio is calculated by the current image index divided by the total number of images. The function packs all the pie chart images into an animated image and returns it to the caller.
-(UIImage *)loadImage {
UIImage * image;
float start = 0.0;
float end = 0.0;
int totalImages = 100;
float imageSize = 100;
float outlineWidth = 5.0;
UIColor * color = [UIColor cyanColor];
NSMutableArray * animationFrames = [NSMutableArray array];
for( int i = 0; i <= totalImages; i++)
{
end = (float)i/(float)totalImages;
image = [PieChart PieChartImageBySize:imageSize outlineWidth:outlineWidth
pieColor:color startRatio:start endRatio:end];
[animationFrames addObject:image];
}
float animationDuration = 0.0;
return [UIImage animatedImageWithImages:animationFrames duration:animationDuration];
}
Next, we will configure the PieChartPicker
and PercentageLabel
.
- Open the interface.storyboard file in the PieChart WatchKit App folder.
- Click the "Show Assistant Editor" icon on the top menu and select the InterfaceController.m file
- CTRL-dragging the picker and label from the storyboard to the
@interface
section in the InterfaceController.m file. We will name the picker as PieChartPicker
and the label as PercentageLabel
.
CTRL-drag the picker to the @implementation
section to add a function to change the percentage value displayed by the PercentageLabel
.
Add the following line in the pickerValueChanged
function so that the label will display the new percentage value when the digital crown is scrolled.
self.PercentageLabel.text = [NSString stringWithFormat:@"%d%%", value];
Finally, add the following code to the awakeWithContext
to initialize the picker at watch app start up time.
- Generate the animated images by calling the
loadImage
function.
- Load the picker:
- ?the
for
-loop populates the pickerItems
array and stores the pie chart image in the contentImage
field of the picker item.
- the
PieChartPicker
is initialized with pie chart images by the call to setItems:pickerItems
- Set the initial pie chart to be 30%.
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
UIImage *image = [self loadImage];
NSMutableArray * pickerItems = [NSMutableArray array];
for( int i=0; i< image.images.count; i++)
{
WKPickerItem * item = [[WKPickerItem alloc]init];
item.contentImage = [WKImage imageWithImage:[image.images objectAtIndex:i]];
[pickerItems addObject:item];
}
[self.PieChartPicker setItems:pickerItems];
[self.PieChartPicker setSelectedItemIndex:30];
}
Build and Run!!
You should be able to build and run the app on simulator by selecting the "iPhone X + Apple Watch" scheme and clicking the ? "play" button.
The first time the app may take a little while to load. Simply re-run it if the simulator crashes with error message: cannot find application id. The simulator may also crash if any of the interface objects on the storyboard is not mapped correctly. Right click on the interface controller on the storyboard and correct any incorrect mapping.
The End
Hope you enjoyed the article and it is helpful to you. Happy coding...