iOS Touch Drawing

Manuel Munoz
Level Up Coding
Published in
4 min readMay 23, 2020

--

Image taken from https://code.tutsplus.com/tutorials/ios-sdk-advanced-freehand-drawing-techniques--mobile-15602

In a recent project I’ve been working on, we needed to provide the user a way of drawing on top of a photo by touching the screen. The user would be able to draw freely over the image, and also they would be able to erase any of their previous drawing simply by touching the erase button and then touching the drawing.

As it turns out the hard part was not the drawing itself, since a quick internet search threw a fantastic drawing tutorial in the Ray Wenderlich site, the hardest for us was the erasing of a particular drawing.

UIGraphicsContext

The Ray Wenderlich tutorial has three main functions that comprise most of the drawing logic.

Where lastPoint is being used to save the last known point of the line to draw (in this case the last known point is also the initial point).

Then touchesMoved:

As you can see, drawing a line is actually quite simple, you just create a custom UIView and basically paste this code and that view is almost ready to serve as a drawing canvas for your user.

The problem we had with this approach is that UIGraphicsGetImageFromCurrentImageContext returns a UIImage? with size of view.frame.size and all of the generated images would have the same size and position making it almost impossible to distinguish between one and another, further complicating the erasing of an individual drawing.

Which took us to option number 2:

CAShapeLayer

Using the same touchesBegan/Moved/Ended functions as before we can create CGPaths, which describe all of the points where the user has moved it's finger, then we add this path to a CAShapeLayer and add this new layer as a sublayer of the "canvas" view this way we get images (layers) for each individual drawing.

Now let’s really see how using CGPath compares to using UIGraphicContext .

Show me the code!

To begin with we created a custom view called DrawingView and replaced the touchesBegan function with this:

The touchesMoved function remains the same as previous example and the drawLine gets changed to this:

New drawLine function using CGPath and CAShapeLayer apis

Comparing previous implementation (CGContext) with current implementation (CALayer) you can see they are incredibly similar. They both make almost the same function calls, but in different contexts. Instead of context.move(to: fromPoint), we use currentPath.move(to: fromPoint), instead of context.setLineCap(.round) we now use currentLayer.lineCap = .round.

Finally the touchesEnded function gets changed to this, since we need to render the added sublayers:

At this point the user can draw by moving it’s finger on the screen, each drawing is individual and it’s linked to a CAShapeLayer.

Now how do we erase specific drawings? This is the issue we wanted to solve in the beggining, so let's get to it!

Erasing

First we will create a function that will help us find the layer that contains the touch point:

Now if you have some worked previously with CAShapeLayers you may have used the hitTest function to check if the layer contains a CGPoint.

In our specific case this doesn’t work because for the hitTest function requires that the layer have a frame and we are not assigning one to it, because if the user draws a long diagonal line through the screen the frame that contains the whole drawing would be a really big rectangle which would detect touches even if the finger is far away from the actual drawn line.

The red line is path drawn by the user, the blue line would be the frame of the layer containing the path

Where the red line is the line drawed by the user, and the blue rectangle is the frame.

Another thing that pops up in the previous code is that we are creating an outline of the shapeLayer.path and then calling the contains(point) function on it, why not call the contains function directly on the path, why do we need to create an outline?

Well it turns out that the contains(point) function only works on closed paths, so if the user draws a straight line that funtion would always return false.

The outline is something like this:

As you can see the outline is a closed shape, so it gives us the required precision we want when deleting shapes by tapping.

Great explanation of this here.

Ok let’s carry on, the findLayer function returns the touched layer, so now we need to delete it:

With these two new functions in place, we need a way to switch between drawing and erasing, so we add a control property and use it to limit drawing capabilities in touchesBegan:

and touchesMoved:

In touchesEnded we call the findLayer if isDrawing == false

The result is this:

It looks kind of nice! Well this are the basics for drawing in iOS, the code can be found here.

This is a very simple example of how drawing and erasing works, this code can be further exapanded and refactored to give the user more drawing options like drawing a square or a circle, or showing the angle between two lines. These options will be covered on a following article.

I hope you liked it! Thanks for reading!

--

--