How To Make A Simple Drawing App with UIKit and Swift
Learn how to make your own drawing app, including different colors and brushes, using UIKit and Core Graphics in this tutorial! By Jean-Pierre Distler.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make A Simple Drawing App with UIKit and Swift
25 mins
Update 4/10/2015 Updated for Xcode 6.3 and Swift 1.2
Update note: This tutorial was updated to iOS 8 and Swift by Jean-Pierre Distler. Original post by tutorial team member Abdul Azeem Khan.
At some stage in our lives, we all enjoyed drawing pictures, cartoons, and other stuff.
For me it was using a pen and paper when I was growing up, but these days the old pen and paper has been replaced by the computer 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 it’s pretty easy, thanks to some great drawing APIs available 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 brush stroke widths and opacity,
- create an eraser,
- create a custom RGB color selector, and
- share your drawing!
Grab your pencils and get started; no need to make this introduction too drawn out! :]
Getting Started
Start by downloading the starter project.
Start Xcode, open the project and have a look on the files inside. As you can see I have not done too much work for you. I added all needed images to the asset catalog and created the main view of the app with all needed constraints. The whole project is based on the Single View Application template.
Now open Main.storyboard and have look at the interface. The View Controller Scene has three buttons on the top. As the titles say they will be used to reset or share a drawing and to bring up a settings screen. On the bottom you can see more buttons with pen images and an eraser. They will be used to select 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 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!
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).
Open ViewController.swift and add the following properties to the class:
var lastPoint = CGPoint.zeroPoint
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var brushWidth: CGFloat = 10.0
var opacity: CGFloat = 1.0
var swiped = false
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
,green
, andblue
store the current RGB values of the selected color. -
brushWidth
andopacity
store the brush stroke width and opacity. -
swiped
identifies if the brush stroke is continuous.
The default RGB values are all 0, which means the drawing color will start out as 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! All 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.
Start by adding the following method:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
swiped = false
if let touch = touches.first as? UITouch {
lastPoint = touch.locationInView(self.view)
}
}
touchesBegan
is called when the user puts a finger down on the screen. This is the start of a drawing event, so you first 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 with their finger, you can keep track of where they started. This is, so to speak, where the brush hits the paper! :]
Add the following two methods next:
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
// 1
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
// 2
CGContextMoveToPoint(context, fromPoint.x, fromPoint.y)
CGContextAddLineToPoint(context, toPoint.x, toPoint.y)
// 3
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetLineWidth(context, brushWidth)
CGContextSetRGBStrokeColor(context, red, green, blue, 1.0)
CGContextSetBlendMode(context, kCGBlendModeNormal)
// 4
CGContextStrokePath(context)
// 5
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
// 6
swiped = true
if let touch = touches.first as? UITouch {
let currentPoint = touch.locationInView(view)
drawLineFrom(lastPoint, toPoint: currentPoint)
// 7
lastPoint = currentPoint
}
}
Here’s what’s going on in this method:
- 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”) andtempImageView
(which holds the “line you’re currently drawing”). Here you want to draw intotempImageView
, so you need to set up a drawing context with the image currently in thetempImageView
(which should be empty the first time). - Next, you get the current touch point and then draw a line with
CGContextAddLineToPoint
fromlastPoint
tocurrentPoint
. 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. - Here are all the drawing parameters for brush size and opacity and brush stroke color.
- This is where the magic happens, and where you actually draw the path!
- Next, you need to wrap up the drawing context to render the new line into the temporary image view.
- In
touchesMoved
, you setswiped
to true so you can keep track of whether there is a current swipe in progress. Since this istouchesMoved
, the answer is yes, there is a swipe in progress! You then call the helper method you just wrote to draw the line. - Finally, you update the
lastPoint
so the next touch event will continue where you just left off.
Next, add the final touch handler:
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
if !swiped {
// draw a single point
drawLineFrom(lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(mainImageView.frame.size)
mainImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: 1.0)
tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
}
First, you check if the user is in the middle of a swipe. If not, then it means 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 then that means you can skip drawing that single point – since touchesMoved
was called before, you don’t need to draw any further since this is touchesEnded
.
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.
Okay, 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.
The App of Many Colors
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 tap any button right now, nothing will happen. First, you’ll need to define all the colors. Add the following array property to the class:
let colors: [(CGFloat, CGFloat, CGFloat)] = [
(0, 0, 0),
(105.0 / 255.0, 105.0 / 255.0, 105.0 / 255.0),
(1.0, 0, 0),
(0, 0, 1.0),
(51.0 / 255.0, 204.0 / 255.0, 1.0),
(102.0 / 255.0, 204.0 / 255.0, 0),
(102.0 / 255.0, 1.0, 0),
(160.0 / 255.0, 82.0 / 255.0, 45.0 / 255.0),
(1.0, 102.0 / 255.0, 0),
(1.0, 1.0, 0),
(1.0, 1.0, 1.0),
]
This builds up an array of RGB values, where each array element is a tuple of three CGFloats. The colors here match the order of the colors in the interface as well as each button’s tag.
Next, find pencilPressed
and add the following implementation:
// 1
var index = sender.tag ?? 0
if index < 0 || index >= colors.count {
index = 0
}
// 2
(red, green, blue) = colors[index]
// 3
if index == colors.count - 1 {
opacity = 1.0
}
This is a short method, but let’s look at it step by step:
- First, you need to know which color index the user selected. There are many places this could go wrong – incorrect tag, tag not set, not enough colors in the array – so there are a few checks here. The default if the value is out of range is just black, the first color.
- Next, you set the
red
,green
, andblue
properties. You didn’t know you could set multiple variables with a tuple like that? There’s your Swift tip of the day! :] - The last color is the eraser, so it’s a bit special. The eraser button sets the color to white and opacity to 1.0. As your background color is also white, this will give you a very handy eraser effect!
What? Time for more drawing already? Yup — build 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
All great artists have those moments where they step back and shake their 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 for this.
Find reset()
and fill in the implementation as follows:
mainImageView.image = nil
That’s it, believe it or not! All the code above does is set the mainImageView
‘s image nil, and — voila — your canvas is cleared! Remember, you drew lines into the image view’s image context, so clearing that out to nil here will reset everything.
Build and run your code again. Draw something, and then tap the Reset button to clear your drawing. There! No need to go tearing up canvases in frustration.
Finishing Touches — Settings
Okay! You now have a functional drawing app, but there’s still that second screen of settings to deal with!
First, open SettingsViewController.swift and add the following two properties to the class:
var brush: CGFloat = 10.0
var opacity: CGFloat = 1.0
This will let you keep track of the brush size and opacity the user selects.
Now add the following implementation to sliderChanged()
:
if sender == sliderBrush {
brush = CGFloat(sender.value)
labelBrush.text = NSString(format: "%.2f", brush.native) as String
} else {
opacity = CGFloat(sender.value)
labelOpacity.text = NSString(format: "%.2f", opacity.native) as String
}
drawPreview()
In the code above, as the slider control changes, the slider values will change appropriately to match. Then you’ll need to update those preview images in drawPreview
, which you’ll add next!
Add the implementation for drawPreview
:
func drawPreview() {
UIGraphicsBeginImageContext(imageViewBrush.frame.size)
var context = UIGraphicsGetCurrentContext()
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetLineWidth(context, brush)
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0)
CGContextMoveToPoint(context, 45.0, 45.0)
CGContextAddLineToPoint(context, 45.0, 45.0)
CGContextStrokePath(context)
imageViewBrush.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIGraphicsBeginImageContext(imageViewBrush.frame.size)
context = UIGraphicsGetCurrentContext()
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetLineWidth(context, 20)
CGContextMoveToPoint(context, 45.0, 45.0)
CGContextAddLineToPoint(context, 45.0, 45.0)
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, opacity)
CGContextStrokePath(context)
imageViewOpacity.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
This method uses the same techniques to draw a preview of the settings like ViewController
uses in the touch handling methods. In both cases, the method draws a single point rather than a line with the appropriate line width and opacity from the slider values.
Build and run your code, open the Settings screen, and play around with the sliders. You will see that the preview images and value labels change as you move it now!
Settings Integration
There’s still one important piece missing here. Did you notice what it was?
The updated opacity and width values are still not being applied to the ViewController drawing canvas! That’s because you have not yet communicated the values specified in the Settings screen to the ViewController yet. This is a perfect job for a delegate protocol.
Open SettingsViewController.swift file and add the following code just below the imports:
protocol SettingsViewControllerDelegate: class {
func settingsViewControllerFinished(settingsViewController: SettingsViewController)
}
This will define a class protocol with one required method. This will be the way for the settings screen to communicate back with any interested party on what the settings are.
Also add a property to the SettingsViewController
class:
weak var delegate: SettingsViewControllerDelegate?
This will hold the reference to the delegate. If there is a delegate, you’ll need to notify it when the user taps the Close button. Find close()
and add the following line to the end of the method:
self.delegate?.settingsViewControllerFinished(self)
This will call the delegate method so it can update itself with the new values.
Now, open ViewController.swift and add a new class extension for the protocol to the bottom of the file:
extension ViewController: SettingsViewControllerDelegate {
func settingsViewControllerFinished(settingsViewController: SettingsViewController) {
self.brushWidth = settingsViewController.brush
self.opacity = settingsViewController.opacity
}
}
This declares the class as conforming to SettingsViewControllerDelegate
and implements its one method. In the implementation, all it needs to do is set the current brush width and opacity to the values from the settings view’s slider controls.
When the user moves from the drawing to the settings, you’ll want the sliders to show the currently-selected values for brush size and opacity. That means you’ll need to pass them along when you open the settings.
Add the following method override to the class:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let settingsViewController = segue.destinationViewController as! SettingsViewController
settingsViewController.delegate = self
settingsViewController.brush = brushWidth
settingsViewController.opacity = opacity
}
When the user triggers the segue by tapping the Settings button, this method will then configure the new SettingsViewController
by setting itself as the delegate and passing the current brush and opacity settings through.
Build and run! At this stage, you will see the brush and opacity values are now updated after you change them in the settings screen. Now you can draw with many colors of different brush sizes and opacity levels!
Finishing Touches — A Custom Color Selector
Currently, you have 10 color buttons on your drawing canvas screen. However, with the custom RGB color selector, the discriminating artists using your app will have the ability to control to pick any available color from the RGB range.
There are a set of RGB color sliders in the settings screen that you will implement next.
Since you’ve provided a preview of the brush size and opacity, you might as well provide a preview of the new brush color! :] The preview will be shown in both preview image views – as well, the opacity and brush preview will be shown in the RGB color. No need for an extra image; you’ll re-use what you already have!
Open SettingsViewController.swift
and add the following properties:
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
You’ll use these to save the current RGB values.
Now add the implementation of colorChanged
:
red = CGFloat(sliderRed.value / 255.0)
labelRed.text = NSString(format: "%d", Int(sliderRed.value)) as String
green = CGFloat(sliderGreen.value / 255.0)
labelGreen.text = NSString(format: "%d", Int(sliderGreen.value)) as String
blue = CGFloat(sliderBlue.value / 255.0)
labelBlue.text = NSString(format: "%d", Int(sliderBlue.value)) as String
drawPreview()
This is the method that will be called when you move any of the RGB sliders. In above code, notice how all you’re doing is updating the property values and updating the labels.
If you build and run your project now you will notice that your color changes will not be shown in the previews. To show them you need a small change in drawPreview()
. Search the lines that call CGContextSetRGBStrokeColor
and replace all 0.0 values with the variables red, green and blue.
In the first half of the method, replace the call to CGContextSetRGBStrokeColor
with the following:
CGContextSetRGBStrokeColor(context, red, green, blue, 1.0)
And in the second half, replace the call to CGContextSetRGBStrokeColor
with the following:
CGContextSetRGBStrokeColor(context, red, green, blue, opacity)
Now that you have the brush and opacity samples drawing with all the correct settings, you’ll want to show them right when the settings screen appears. Add the following implementation of add viewWillAppear
to the class:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
sliderBrush.value = Float(brush)
labelBrush.text = NSString(format: "%.1f", brush.native) as String
sliderOpacity.value = Float(opacity)
labelOpacity.text = NSString(format: "%.1f", opacity.native) as String
sliderRed.value = Float(red * 255.0)
labelRed.text = NSString(format: "%d", Int(sliderRed.value)) as String
sliderGreen.value = Float(green * 255.0)
labelGreen.text = NSString(format: "%d", Int(sliderGreen.value)) as String
sliderBlue.value = Float(blue * 255.0)
labelBlue.text = NSString(format: "%d", Int(sliderBlue.value)) as String
drawPreview()
}
As you can see this method just presets all labels and sliders with the correct values. The drawPreview
call ensures that the preview image views also show the correct thing.
Finally, open ViewController.swift. As before, you’ll need to make sure the current color make it across to the settings screen so add the following lines to the end of prepareForSegue
:
settingsViewController.red = red
settingsViewController.green = green
settingsViewController.blue = blue
This will pass on the current red, green and blue values so the RGB sliders are set correctly.
Finally, find settingsViewControllerFinished
in the class extension and add the following lines to that method:
self.red = settingsViewController.red
self.green = settingsViewController.green
self.blue = settingsViewController.blue
In the above code, as the SettingsViewController
closes, the updated RGB values are being fetched as well.
All right — time for another build and run stage! Put the Color Picker through its paces. The selected RGB color, which is displayed in RGBPreview, is now the default brush stroke color on the drawing canvas!
But what good is all of these wonderful works of art if you can’t share them with the world? Since you can’t stick the pictures up on your refrigerator, you’ll share them with the world in the final step of this tutorial! :]
Finishing Touches – Share and Enjoy!
In this final step, you’ll use the iOS share sheet to send your works of art out there into the world!
There are two parts to this: first, you need to get the drawing as a UIImage
object; then, you just pass it on to UIActivityViewController
to decides which sharing options will work best depending on what accounts and services are available.
In ViewController.swift, add the following implementation to share()
:
UIGraphicsBeginImageContext(mainImageView.bounds.size)
mainImageView.image?.drawInRect(CGRect(x: 0, y: 0,
width: mainImageView.frame.size.width, height: mainImageView.frame.size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let activity = UIActivityViewController(activityItems: [image], applicationActivities: nil)
presentViewController(activity, animated: true, completion: nil)
This method is pretty simple – first it renders out the drawing from mainImageView
to a new UIImage
. Then, UIActivityViewController
does most of the heavy lifting! All you need to do is pass it an array of things to share; in this case, that’s just the single image.
The second parameter to the initializer applicationActivities
lets you limit the activities, so passing nil
means iOS will provide as many share options as possible. Your drawing deserves no less!
Build and run the app, and create your masterpiece! When you hit Share, you’ll now be able to let the world know of your talents.
Where To Go From Here?
Here is the DrawPad-final-6_3 with all of the code from the above tutorial.
You can play around a bit more with the brush strokes and also investigate the drawing of arcs and rectangles using Quartz2D. A good start would be to have a look at Quartz 2D Programming Guide” . There are number of beginner and advanced-level concepts there with which you can play around to create awesome shapes and patterns.
If you want to learn how to draw more smooth lines, you should also check out this smooth line drawing article by Krzysztof Zablocki. It’s based on Cocos2D but you can use the same technique in UIKit.
I hope you enjoyed this tutorial as much as I did! Feel free to post your questions and comments on the forum — and feel free to share your masterpieces you created with your new app! :]
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more