iOS – Paintbrush App for the iPhone

How do I draw free-form shapes on the iPhone screen with my finger? This post is an introduction to a simple paintbrush style app that does exactly that. Actually, this type of app is much more suitable for an iPad, but we’ll stick to the iPhone for now.

Screen Shot 2014-05-15 at 5.04.44 PM

In the above app, you simply place your finger inside the black UIView window and start drawing whatever you want. The default drawing color is white. To change to a different color, just click on the appropriate color buttons. To keep things simple, there are only 5 color options.

How it works under the hood

The heart of this app is a set of pre-built methods that can sense touch events and return the coordinates (relative to the view) where your finger touches and interacts with the screen. There are three primary touch events:

  • Your finger touches the screen for the first time (touch begins)
  • Your finger moves from point A to point B on the screen
  • Your finger leaves the screen (touch ends)

This is how these methods appear in actual code:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

The methods are inserted in the View Controller implementation, and the usual practice is to override the default implementations (which do nothing) with our own custom implementation.

Like the other apps we developed so far, the ViewController interface contains IBOutlets, IBAction items from the storyboard and other properties used in the implementation. Here is the ViewController interface for this app:

#import 
#import "Circle.h"

@interface ViewController : UIViewController

@property (strong, nonatomic) IBOutlet UIView *blackBox;
@property (strong, nonatomic) NSMutableArray* circleArray;
@property int i;
@property UIColor* currentColor;

- (IBAction)redColor:(id)sender;
- (IBAction)blueColor:(id)sender;
- (IBAction)yellowColor:(id)sender;
- (IBAction)greenColor:(id)sender;
- (IBAction)whiteColor:(id)sender;
- (IBAction)clearScreen:(id)sender;

@end

The implementation file contains the meat of the app and I encourage you to uncomment some of the NSLog calls to get a feel of the numbers (x and y coordinates) returned by the three touch methods.

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize blackBox;
@synthesize circleArray;
@synthesize i;
@synthesize currentColor;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    blackBox.clipsToBounds = YES;
    circleArray = [[NSMutableArray alloc] initWithCapacity:1000];
    i = 0;
    currentColor = [UIColor whiteColor];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        //NSLog(@"touchedBegan at %f %f", touchLocation.x, touchLocation.y);
    }
}

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        //NSLog(@"touchesMoved at %f %f", touchLocation.x, touchLocation.y);
        float x = touchLocation.x;
        float y = touchLocation.y;
        float R = 10.0;
        CGRect circleFrame = CGRectMake(x-R, y-R, 2*R, 2*R);
        Circle* circle = [[Circle alloc] initWithFrame:circleFrame];
        [circle setBackgroundColor:[UIColor clearColor]];
        circle.circleColor = currentColor;
        [circleArray addObject:circle];
        [blackBox addSubview:circleArray[i]];
        i++;
    }
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* t in touches) {
        CGPoint touchLocation;
        touchLocation = [t locationInView:blackBox];
        //NSLog(@"touchesEnded at %f %f", touchLocation.x, touchLocation.y);
    }
}

- (IBAction)clearScreen:(id)sender {
    for (int kount=0; kount<i; kount++) {
        [circleArray[kount] removeFromSuperview];
    }
    [circleArray removeAllObjects];
    circleArray = nil;
    circleArray = [[NSMutableArray alloc] initWithCapacity:1000];
    i = 0;
}

- (IBAction)redColor:(id)sender {
    currentColor = [UIColor redColor];
}

- (IBAction)blueColor:(id)sender {
    currentColor = [UIColor blueColor];
}

- (IBAction)yellowColor:(id)sender {
    currentColor = [UIColor yellowColor];
}

- (IBAction)greenColor:(id)sender {
    currentColor = [UIColor greenColor];
}

- (IBAction)whiteColor:(id)sender {
    currentColor = [UIColor whiteColor];
}


@end

Once the finger coordinates (x, y) are obtained using these methods, we allocate a Circle object (subclass of UIView) that draws a filled circle of a certain size at that (x, y) location. As your finger moves, the coordinates keep changing and we scramble more and more Circle objects to draw additional circles along the path. If you move your finger too quickly, you will actually see the individual circles. Go slow if you want to create the illusion of drawing a continuous thick line.

Notice that we use a NSMutableArray to keep track of all the Circle objects because later on, we want to clear the entire screen by removing all those views from the superview. The NSMutableArray is initialized with a maximum capacity to hold 1000 objects. Obviously, as we draw more and more circles, the size of the NSMutableArray and the memory needed by the app increases. Because of this reason, you will notice some sluggishness in the drawing when you draw far too many shapes in the view. All this memory is released when we hit the CLEAR button (triggering the clearScreen method above).

Finally, here is the interface and implementation file for the Circle class:

#import <UIKit/UIKit.h>

@interface Circle : UIView
@property UIColor* circleColor;
@end

Clicking the color buttons in the UI triggers methods that change the circleColor property. Background images of the appropriate color were used for all the color buttons.

#import "Circle.h"

@implementation Circle

@synthesize circleColor;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

// Custom drawing: circle filled with the specified color
- (void)drawRect:(CGRect)rect
{
    // get the current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // context size in pixels
    size_t WIDTH = CGBitmapContextGetWidth(context);
    size_t HEIGHT = CGBitmapContextGetHeight(context);
    
    // for retina display, 1 point = 2 pixels
    // context size in screen points
    float width = WIDTH/2.0;
    float height = HEIGHT/2.0;
    
    // center coordinates
    float xCen = width/2.0;
    float yCen = height/2.0;
    float maxR = width/2.0;     // WIDTH = HEIGHT in this case

    // circle of specified color
    CGContextBeginPath(context);
    CGContextAddArc(context, xCen, yCen, maxR, 0, 2*M_PI, YES);
    [circleColor setFill];
    CGContextDrawPath(context, kCGPathFill);
}

@end

If you wish to reduce the thickness of the lines in your drawing, simply reduce the frame size when you draw the Circle object. The size I am presently using is 20 points (circle radius = 10).

In summary, this post was a quick introduction to the world of sensing touch events in iOS and using this information to create a simple paintbrush-style app. Want some suggestions for using this app? How about teaching toddlers how to draw numbers and letters in English or in your native alphabet?

As always, you can download the entire source code for this project from my GitHub link:

https://github.com/jabhiji/ios-touch-paintbrush.git

Happy Xcoding!

One thought on “iOS – Paintbrush App for the iPhone”

  1. Just added an ERASE button that lets you selectively rub out parts of your drawing. This is essentially implemented by drawing black circles along the “erase path”.

Leave a Reply

Your email address will not be published. Required fields are marked *