UIKit Drawing Tutorial: How to Make a Simple Drawing App

Learn how to create a simple drawing app in Swift using UIKit drawing APIs. By Ron Kliffer.

Leave a rating/review
Download materials
Save for later
Share
Update note: Ron Kliffer updated this tutorial for Xcode 10, Swift 4.2 and iOS 12. Abdul Azeem Khan wrote the original and Jean-Pierre Distler completed a previous update.

At some stage in our lives, we all enjoyed drawing pictures, cartoons and other stuff.

For me, it was using a pen and paper but, these days, the old pen and paper have been replaced by computers and mobile devices! Drawing can be especially fun on touch-based devices, as you can see by the abundance of drawing apps on the App Store.

Want to learn how to make a drawing app of your own? The good news is that it’s pretty easy, thanks to some great drawing APIs available through UIKit in iOS.

In this tutorial, you will create an app very much like Color Pad for iPhone. In the process you’ll learn how to:

  • Draw lines and strokes using Quartz2D.
  • Use multiple colors.
  • Set the brush stroke width and opacity.
  • Create an eraser.
  • Create a custom RGB color selector.
  • Share your drawing!

Grab your pencils and get started; no need to make this introduction too drawn out! :]

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project.

Start Xcode, open the starter project and have a look at the files inside. As you can see, not much work has been done for you. You have all the needed images in the asset catalog and the main view of the app with all needed constraints set. The whole project is based on the Single View Application template.

Open Main.storyboard and look at the interface. The initial ViewController scene has three buttons on the top. As the titles suggest, you will use these to reset or share a drawing and to bring up a Settings screen. On the bottom, you can see more buttons with pencil images and an eraser. You’ll use these to select predefined colors.

Finally, there are two image views called MainImageView and TempImageView — you’ll see later why you need two image views when you use them to allow users to draw with different brush opacity levels.

The View Controller window has the actions and outlets set as you’d expect: Each button at the top has an action, the pencil colors all link to the same action — they have different tags set to tell them apart — and there are outlets for the two image views.

In order to let your inner artist shine, you’ll need to add some code!

Being Quick on the Draw

You’ll begin 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!

Open ViewController.swift and add the following properties to the class:

var lastPoint = CGPoint.zero
var color = UIColor.black
var brushWidth: CGFloat = 10.0
var opacity: CGFloat = 1.0
var swiped = false

Here’s a quick explanation of these variables:

  • lastPoint stores the last drawn point on the canvas. You’ll need this when your user draws a continuous brush stroke on the canvas.
  • color stores the current selected color. It defaults to black.
  • brushWidth stores the brush stroke width. It defaults to 10.0.
  • opacity stores the brush opacity. It defaults to 1.0.
  • swiped indicates if the brush stroke is continuous.

Now for the drawing part! All touch-notifying methods come from the parent class UIResponder; they fire in response to a touch beginning, moving or ending. You’ll use these three methods to implement your drawing logic.

Start by adding the following method:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  guard let touch = touches.first else {
    return
  }
  swiped = false
  lastPoint = touch.location(in: view)
}

The system calls touchesBegan(_:with:) when the user puts a finger down on the screen. This is the start of a drawing event, so you first make sure you indeed received a touch. You then reset swiped to false since the touch hasn’t moved yet. You also save the touch location in lastPoint so when the user starts drawing, you can keep track of where the stroke started. This is, so to speak, where the brush hits the paper. :]

Add the following two methods next:

func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
  // 1
  UIGraphicsBeginImageContext(view.frame.size)
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }
  tempImageView.image?.draw(in: view.bounds)
    
  // 2
  context.move(to: fromPoint)
  context.addLine(to: toPoint)
  
  // 3  
  context.setLineCap(.round)
  context.setBlendMode(.normal)
  context.setLineWidth(brushWidth)
  context.setStrokeColor(color.cgColor)
  
  // 4  
  context.strokePath()
  
  // 5 
  tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
  tempImageView.alpha = opacity   
  UIGraphicsEndImageContext()
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
  guard let touch = touches.first else {
    return
  }

  // 6
  swiped = true
  let currentPoint = touch.location(in: view)
  drawLine(from: lastPoint, to: currentPoint)
    
  // 7
  lastPoint = currentPoint
}

Here’s what’s going on in these methods:

  1. The first method is responsible for drawing a line between two points. Remember that this app has two image views: mainImageView, which holds the “drawing so far”, and tempImageView, which holds the “line you’re currently drawing”. Here, you want to draw into tempImageView, so you need to set up a drawing context with the image currently in the tempImageView, which should be empty the first time.
  2. Next, you get the current touch point and then draw a line from lastPoint to currentPoint. You might think that this approach will produce a series of straight lines and the result will look like a set of jagged lines. This will produce straight lines, but the touch events fire so quickly that the lines are short enough and the result will look like a nice smooth curve.
  3. Here, you set some drawing parameters for brush size and stroke color. If you’re interested in learning more about these, check out Apple’s documentation for CGContext.
  4. This is where the magic happens and where you actually draw the path!
  5. Next, you need to wrap up the drawing context to render the new line into the temporary image view. You get the image representation of the context and set it to the image property of tempImageView.
  6. The system calls touchesMoved(_:with:) when the user drags a finger along the screen. Here, you set swiped to true so you can keep track of whether there is a current swipe in progress. Since this is touchesMoved, the answer is yes, there is a swipe in progress! You then call the helper method you just wrote to draw the line.
  7. Finally, you update lastPoint so the next touch event will continue where you just left off.

Next, add the final touch handler:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
  if !swiped {
    // draw a single point
    drawLine(from: lastPoint, to: lastPoint)
  }
    
  // Merge tempImageView into mainImageView
  UIGraphicsBeginImageContext(mainImageView.frame.size)
  mainImageView.image?.draw(in: view.bounds, blendMode: .normal, alpha: 1.0)
  tempImageView?.image?.draw(in: view.bounds, blendMode: .normal, alpha: opacity)
  mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
    
  tempImageView.image = nil
}

iOS calls touchesEnded(_:with:) when the user lifts his finger off the screen. Here, you first check if the user is in the middle of a swipe. If not, then it means that the user just tapped the screen to draw a single point. In that case, just draw a single point using the helper method you wrote earlier.

If the user was in the middle of a swipe, that means you can skip drawing that single point — since touchesMoved(_:with:) was called before, you don’t need to draw any further.

The final step is to merge the tempImageView with mainImageView. You drew the brush stroke on tempImageView rather than on mainImageView. What’s the point of an extra UIImageView; can’t you just draw directly to mainImageView?

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

OK, time to get drawing! Build and run your app. You will see that you can now draw pretty black lines on your canvas!

That’s a great start! With just those touch handling methods you have a huge amount of the functionality in place. Now it’s time to fill in some more options, starting with color.