This article is part of a 3-part series on how to build a simple painting app for different mobile platforms. The aim is to explain to readers how a simple programming concept has to evolve to create a good user experience with high performance. The series is available for iOS, Android, as well as Adobe AIR for mobile.
The Naive Approach to Painting in iOS
The starting approach to painting on iOS is to simply capture touch events and draw a line between the points. We can create and attach a custom UIView to the root view controller to handle all this functionality.
// in our root view controller
- (void)viewDidLoad {
[super viewDidLoad];
PaintView *paint = [[PaintView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:paint];
[paint release];
}
// PaintView.m
@implementation PaintView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
hue = 0.0;
}
return self;
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
touch = [touches anyObject];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
if(touch != nil){
CGContextRef context = UIGraphicsGetCurrentContext();
hue += 0.005;
if(hue > 1.0) hue = 0.0;
UIColor *color = [UIColor colorWithHue:hue saturation:0.7 brightness:1.0 alpha:1.0];
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 15);
CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint newPoint = [touch locationInView:self];
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(context, newPoint.x, newPoint.y);
CGContextStrokePath(context);
}
}
@end

The code is very straightforward and appears to do exactly what we want. When a touch is captured, we save the touch object and ask the screen to update its display which will call (void)drawRect to draw a new segment of line. Unfortunately when you run the code you’ll see a big problem with this solution. The painting will flicker as you draw your finger along the screen.
The reason this happens is that the render engine is double buffered, so each time you issue a call to draw a line segment, it tick-tocks between the two buffers. Our naive attempt to draw to the screen needs a new solution to work properly.
Next Step: Adding in a Backing Store
The way to solve this problem is to draw to a backing store first, then when we want to update the screen we simply draw that backing store to the front buffer. To handle the backing store we need to create a new cgcontext tied to a bitmap.
@implementation PaintView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
hue = 0.0;
[self initContext:frame.size];
}
return self;
}
- (BOOL) initContext:(CGSize)size {
int bitmapByteCount;
int bitmapBytesPerRow;
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (size.width * 4);
bitmapByteCount = (bitmapBytesPerRow * size.height);
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
cacheBitmap = malloc( bitmapByteCount );
if (cacheBitmap == NULL){
return NO;
}
cacheContext = CGBitmapContextCreate (cacheBitmap, size.width, size.height, 8, bitmapBytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);
return YES;
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[self drawToCache:touch];
}
- (void) drawToCache:(UITouch*)touch {
hue += 0.005;
if(hue > 1.0) hue = 0.0;
UIColor *color = [UIColor colorWithHue:hue saturation:0.7 brightness:1.0 alpha:1.0];
CGContextSetStrokeColorWithColor(cacheContext, [color CGColor]);
CGContextSetLineCap(cacheContext, kCGLineCapRound);
CGContextSetLineWidth(cacheContext, 15);
CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint newPoint = [touch locationInView:self];
CGContextMoveToPoint(cacheContext, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(cacheContext, newPoint.x, newPoint.y);
CGContextStrokePath(cacheContext);
[self setNeedsDisplay];
}
- (void) drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef cacheImage = CGBitmapContextCreateImage(cacheContext);
CGContextDrawImage(context, self.bounds, cacheImage);
CGImageRelease(cacheImage);
}
@end
When the view is created we create a cached bitmap and cached context to handle all of our touch drawing, then in our drawRect callback we now grab a image snapshot of the cached context to draw to the front buffer. The full drawing is now shown every time you move your finger around the display.
Next Step: Optimizing the Drawing Code
At this point we have a working solution, but we don’t have a good solution. Depending on the hardware you run this on, you may notice that the drawing is having a hard time keeping up with your finger. The reason why is because we’re wasting a lot of time updating areas of the screen that aren’t changing, we’re simply rendering everything in the backing store 50 to 60 times per second. To solve this we want to tell the draw routine to only update the parts of the display that have changed. To do this we create a bounding box around the line being drawn and pass that box on to the drawRect routine.
- (void) drawToCache:(UITouch*)touch {
hue += 0.005;
if(hue > 1.0) hue = 0.0;
UIColor *color = [UIColor colorWithHue:hue saturation:0.7 brightness:1.0 alpha:1.0];
CGContextSetStrokeColorWithColor(cacheContext, [color CGColor]);
CGContextSetLineCap(cacheContext, kCGLineCapRound);
CGContextSetLineWidth(cacheContext, 15);
CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint newPoint = [touch locationInView:self];
CGContextMoveToPoint(cacheContext, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(cacheContext, newPoint.x, newPoint.y);
CGContextStrokePath(cacheContext);
CGRect dirtyPoint1 = CGRectMake(lastPoint.x-10, lastPoint.y-10, 20, 20);
CGRect dirtyPoint2 = CGRectMake(newPoint.x-10, newPoint.y-10, 20, 20);
[self setNeedsDisplayInRect:CGRectUnion(dirtyPoint1, dirtyPoint2)];
}
We now have a bounding box around our touch points that includes a bit of padding to accommodate the line width. At this point we have a very simple yet high performance painting app. For many people this may be the last step needed to understand how to build a general painting app. However, there a few more concepts we can dive into to make the app better.
Creating smooth lines
If you run the app you’ll likely notice that all of the lines being drawn are very jagged. This is because we’re simply drawing straight lines between each touch point. Since touch events only fire about 50 times per second we need to use some interpolation to smooth out the lines in between those touch points.
We can solve this problem using bezier curves, but it comes at a cost. In order to know where to curve our lines to, we always need to calculate curves one frame behind the current touch point. In the example below you can see that the curve between touch 1 and touch 2 isn’t known until touch 3 is provided.

To create clean continuous lines we ultimately want to track a total of 4 touch points in order to create our control points for the line. I’m using the formula available at http://www.antigrain.com/research/bezier_interpolation/ to determine those control points, and feeding it into the code below.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
point0 = CGPointMake(-1, -1);
point1 = CGPointMake(-1, -1); // previous previous point
point2 = CGPointMake(-1, -1); // previous touch point
point3 = [touch locationInView:self]; // current touch point
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
point0 = point1;
point1 = point2;
point2 = point3;
point3 = [touch locationInView:self];
[self drawToCache];
}
- (void) drawToCache {
if(point1.x > -1){
hue += 0.005;
if(hue > 1.0) hue = 0.0;
UIColor *color = [UIColor colorWithHue:hue saturation:0.7 brightness:1.0 alpha:1.0];
CGContextSetStrokeColorWithColor(cacheContext, [color CGColor]);
CGContextSetLineCap(cacheContext, kCGLineCapRound);
CGContextSetLineWidth(cacheContext, 15);
double x0 = (point0.x > -1) ? point0.x : point1.x; //after 4 touches we should have a back anchor point, if not, use the current anchor point
double y0 = (point0.y > -1) ? point0.y : point1.y; //after 4 touches we should have a back anchor point, if not, use the current anchor point
double x1 = point1.x;
double y1 = point1.y;
double x2 = point2.x;
double y2 = point2.y;
double x3 = point3.x;
double y3 = point3.y;
double xc1 = (x0 + x1) / 2.0;
double yc1 = (y0 + y1) / 2.0;
double xc2 = (x1 + x2) / 2.0;
double yc2 = (y1 + y2) / 2.0;
double xc3 = (x2 + x3) / 2.0;
double yc3 = (y2 + y3) / 2.0;
double len1 = sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
double len2 = sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
double len3 = sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));
double k1 = len1 / (len1 + len2);
double k2 = len2 / (len2 + len3);
double xm1 = xc1 + (xc2 - xc1) * k1;
double ym1 = yc1 + (yc2 - yc1) * k1;
double xm2 = xc2 + (xc3 - xc2) * k2;
double ym2 = yc2 + (yc3 - yc2) * k2;
double smooth_value = 0.5;
float ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
float ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;
float ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
float ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;
CGContextMoveToPoint(cacheContext, point1.x, point1.y);
CGContextAddCurveToPoint(cacheContext, ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, point2.x, point2.y);
CGContextStrokePath(cacheContext);
CGRect dirtyPoint1 = CGRectMake(point1.x-10, point1.y-10, 20, 20);
CGRect dirtyPoint2 = CGRectMake(point2.x-10, point2.y-10, 20, 20);
[self setNeedsDisplayInRect:CGRectUnion(dirtyPoint1, dirtyPoint2)];
}
}
Now our app produces nice smooth lines that track relatively well to the intended path that the user naturally drew with their finger.

Adding multi-touch to this app is as simple as handling each individual touch inside the touchesMoved:(NSSet*)touches callback. Advanced drawing apps would likely move most of the drawing code to an OpenGL surface for even faster drawing, but that is beyond the scope of this example.
XCode Files:
Simple Painting with jagged lines
Advanced Painting with smooth lines
In the second part of the series we’ll walk through the same ideas above and see how they would be implemented on an Android device.