How To Make A Simple Drawing App with UIKit

This is a blog post by iOS Tutorial Team member Abdul Azeem, software architect and co-founder at Datainvent Systems, a software development and IT services company. At some stage in all of our lives, we enjoyed drawing pictures, cartoons, and other stuff. For me it was using a pen and paper when I was growing […] By .

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

Quick on the Draw

Your app will start off with a simple Drawing Feature, whereby you can swipe your finger on the screen to draw simple black lines. (Hey, even Picasso started with the basics).

Add the following instance variables so that your ViewController.h @interface section looks like this:

@interface ViewController : UIViewController {
    
    CGPoint lastPoint;
    CGFloat red;
    CGFloat green;
    CGFloat blue;
    CGFloat brush;
    CGFloat opacity;
    BOOL mouseSwiped;
}

Here’s a quick explanation of the variables used above:

  • lastPoint stores the last drawn point on the canvas. This is used when a continuous brush stroke is being drawn on the canvas.
  • red, blue, and green store the current RGB values of the selected color.
  • brush and opacity store the brush stroke width and opacity.
  • mouseSwiped identifies if the brush stroke is continuous.

Go to ViewController.m and initialize these variables to some reasonable defaults in viewDidLoad, as below:

- (void)viewDidLoad
{
    red = 0.0/255.0;
    green = 0.0/255.0;
    blue = 0.0/255.0;
    brush = 10.0;
    opacity = 1.0;
    
    [super viewDidLoad];
}

Above, the RGB values are being initialized to black just for now. The default opacity is set to 1.0 and line width is set to 10.0.

Now for the drawing part! Add the following three functions to the ViewController.m file:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    mouseSwiped = NO;
    UITouch *touch = [touches anyObject];
    lastPoint = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    
    mouseSwiped = YES;
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self.view];
    
    UIGraphicsBeginImageContext(self.view.frame.size);
    [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush );
    CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
    CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
    
    CGContextStrokePath(UIGraphicsGetCurrentContext());
    self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
    [self.tempDrawImage setAlpha:opacity];
    UIGraphicsEndImageContext();
    
    lastPoint = currentPoint;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    
    if(!mouseSwiped) {
        UIGraphicsBeginImageContext(self.view.frame.size);
        [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush);
        CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, opacity);
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
        CGContextStrokePath(UIGraphicsGetCurrentContext());
        CGContextFlush(UIGraphicsGetCurrentContext());
        self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }
    
    UIGraphicsBeginImageContext(self.mainImage.frame.size);
    [self.mainImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0];
    [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:opacity];
    self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext();
    self.tempDrawImage.image = nil;
    UIGraphicsEndImageContext();
}

Phew! That’s a lot of code, but don’t worry we’ll go over the important parts bit by bit.

These touch-notifying methods come from the parent class UIResponder; they are fired in response to touches began, moved, and ended events. You’ll use these three methods to implement your drawing logic.

1) In touchesBegan the lastPoint variable is initialized to the current touch point.This is, so to speak, where the brush hits the paper! :]

lastPoint = [touch locationInView:self.view];

2) In touchesMoved, you get the current touch point and then draw a line with CGContextAddLineToPoint from lastPoint to currentPoint. You’re right to think that this approach will produce a series of straight lines, but the lines are sufficiently short that the result looks like a nice smooth curve.

CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y); 

Now set our brush size and opacity and brush stroke color:

CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush );
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);

Finish it off by drawing the path:

CGContextStrokePath(UIGraphicsGetCurrentContext());

3) In touchesEnded you check if mouse was swiped. If it was, then it means touchesMoved was called and you don’t need to draw any further. However, if the mouse was not swiped, then it means user just tapped the screen to draw a single point. In that case, just draw a single point.

Once the brush stroke is done, merge the tempDrawImage with mainImage, as below:

[self.mainImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, 
    self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0];
[self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, 
    self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:opacity];
self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext();
self.tempDrawImage.image = nil;

You will notice that the whole brush stroke was drawn on tempDrawImage rather than on mainImage. What’s the point of an extra UIImageView — can’t you just draw directly to mainImage?

You could, but the dual UIImageViews are used to preserve opacity. When you’re drawing on tempDrawImage, the opacity is set to 1.0 (fully opaque). However, when you merge tempDrawImage with mainImage, the tempDrawImage opacity is set to the configured value, thus giving the brush stroke the opacity we want. If you were to draw directly on mainImage, it would be incredibly difficult to draw brush strokes with different opacity values.

Okay, time to get drawing some happy little trees! Save your work, then compile and run. You will see that you can now draw pretty black lines on your canvas!

The App of Many Colors

So far so good! However, it’s time to add a splash of color to the scene – line art alone is kind of drab.

There are 10 color buttons on the screen at the moment, but if you click any button right now, nothing will happen. Let’s fix that.

Go to the pencilPressed function in ViewController.m and modify it to match the following:

- (IBAction)pencilPressed:(id)sender {
    
    UIButton * PressedButton = (UIButton*)sender;
    
    switch(PressedButton.tag)
    {
        case 0:
            red = 0.0/255.0;
            green = 0.0/255.0;
            blue = 0.0/255.0;
            break;
        case 1:
            red = 105.0/255.0;
            green = 105.0/255.0;
            blue = 105.0/255.0;
            break;
        case 2:
            red = 255.0/255.0;
            green = 0.0/255.0;
            blue = 0.0/255.0;
            break;
        case 3:
            red = 0.0/255.0;
            green = 0.0/255.0;
            blue = 255.0/255.0;
            break;
        case 4:
            red = 102.0/255.0;
            green = 204.0/255.0;
            blue = 0.0/255.0;
            break;
        case 5:
            red = 102.0/255.0;
            green = 255.0/255.0;
            blue = 0.0/255.0;
            break;
        case 6:
            red = 51.0/255.0;
            green = 204.0/255.0;
            blue = 255.0/255.0;
            break;
        case 7:
            red = 160.0/255.0;
            green = 82.0/255.0;
            blue = 45.0/255.0;
            break;
        case 8:
            red = 255.0/255.0;
            green = 102.0/255.0;
            blue = 0.0/255.0;
            break;
        case 9:
            red = 255.0/255.0;
            green = 255.0/255.0;
            blue = 0.0/255.0;
            break;
    }
}

Notice that you are now using the tags that were assigned to each pencil button earlier to distinguish them, and to assign the proper RGB value for the pencil.

What? Time for compiling and running already? Yup — compile and run, and get ready to let the colors fly! Now, tapping a color button changes the brush stroke to use that button’s color. No more drab line art!

Tabula Rasa

Every great artist has those moments where he steps back and shakes his head muttering “No! No! This will never do!” You’ll want to provide a way to clear the drawing canvas and start over again. You already have a ‘Reset’ button set up in your app. Complete ViewController’s reset method as follows:

- (IBAction)reset:(id)sender {
    
    self.mainImage.image = nil;
    
}

and thats it! All the code above does is set the mainImage.image to nil, and — voila — your canvas is cleared!

Compile and run your code. Click the Reset button to clear your canvas by clicking the ‘Reset’ button. There! No need to go tearing up canvases in frustration.