Apple Pencil Tutorial: Getting Started
In this Apple Pencil tutorial, you’ll learn about force, touch coalescing, altitude, and azimuth, to add realistic lines and shading to a drawing app. By Caroline Begbie.
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
Apple Pencil Tutorial: Getting Started
30 mins
- Prerequisites
- Getting Started
- Your First Drawing with Pencil
- Smoother Drawing
- Tilting the Pencil
- Altitude, Azimuth and Unit Vectors
- Draw With Shading
- Working With Texture
- Using Azimuth to Adjust Width
- Playing with Opacity
- Finger vs. Pencil
- Faking Force For a Finger
- Reducing Latency
- Housekeeping: Deleting Drawing Predictions
- Where To Go From Here?
Tilting the Pencil
Now you have lovely fluent drawing in your app. However, if you’ve read or watched any reviews of the Apple Pencil, you’ll remember there was talk of its pencil-like shading abilities. All the users need do is tilt it, but little do they realize that shading doesn’t happen automatically — it’s all down to us clever app developers to write the code that makes it work as expected. :]
Altitude, Azimuth and Unit Vectors
In this section, I’ll describe how you measure the tilt. you’ll get to add support for simple shading in the next section.
When you’re working with Pencil, you can rotate it in three dimensions. Up and down direction is called altitude, while side-to-side is called azimuth:
The altitudeAngle
property on UITouch
is new to iOS 9.1, and is there just for the Apple Pencil. It’s an angle measured in radians. When Pencil lies flat on the iPad’s surface, the altitude is 0. When it stands straight up with the point on the screen, the altitude is π/2
. Remember that there are 2π
radians in a 360 degrees circle, so π/2
is equivalent to 90 degrees.
There are two new methods on UITouch
to get azimuth: azimuthAngleInView(_:)
and azimuthUnitVectorInView(_:)
. The least expensive is azimuthUnitVectorInView(_:)
, but both are useful. The best one for your situation depends on what you need to calculate.
You’ll explore how the azimuth’s unit vector works. For reference, a unit vector has a length of 1
and points from the coordinate (0,0)
towards a direction:
To see for yourself, add the following at the top of touchesMoved(_:withEvent:)
, just after the guard
statement:
print(touch.azimuthUnitVectorInView(self))
Build and run. With the iPad in landscape orientation — Scribble is landscape only to keep this tutorial focused on Pencil — hold your pen so that the point is touching on the left side of the screen, and the end is leaning right.
You won’t be able to get these values in the debug console with satisfactory precision, but the vector is approximately 1
unit in the x
direction and 0
units in the y
direction — in other words (1, 0)
.
Rotate Pencil 90 degrees counter-clockwise so the tip is pointing towards the bottom of the iPad. That direction is approximately (0, -1)
.
Note that x
direction uses cosine and the y
direction uses sine. For example, if you hold your pen as in the picture above — about 45 degrees counter-clockwise from your original horizontal direction — the unit vector is (cos(45), sin(-45))
or (0.7071, -0.7071)
.
Note: If you don’t know a lot about vectors, it’s a useful bit of knowledge to pursue. Here’s a two-part tutorial on Trigonometry for Games using Sprite Kit that will help you wrap your head around vectors.
Note: If you don’t know a lot about vectors, it’s a useful bit of knowledge to pursue. Here’s a two-part tutorial on Trigonometry for Games using Sprite Kit that will help you wrap your head around vectors.
Remove that last print
statement when you understand how changing the direction of Pencil gives you the vector that indicates where it’s pointing.
Draw With Shading
Now that you know how to measure tilting, you’re ready to add simple shading to Scribble.
When Pencil is at a natural drawing angle, you draw a line by using force to determine the thickness, but when the user tilts it on its side, you use force to measure the shading’s opacity.
You’ll also calculate the thickness of the line based upon the direction of the stroke and the direction in which you’re holding the Pencil.
If you’re not quite following me here, just go find a pencil and paper to try shading by turning the pencil on its side so that the lead has maximum contact with the paper. When you shade in the same direction as the pencil is leaning, the shading is thin. But when you shade at a 90 degree angle to the pencil, the shading is at its thickest:
Working With Texture
The first order of business is to change the texture of the line so that it looks more like shading with a real pencil. The starter app includes an image in the Asset Catalog called PencilTexture to use for this.
Add this property to the top of CanvasView:
private var pencilTexture = UIColor(patternImage: UIImage(named: "PencilTexture")!)
This will allow you to use pencilTexture
as a color to draw with, instead of the default red color you’ve used up until now.
Find the following line in drawStroke(_:touch:)
:
drawColor.setStroke()
And change it to:
pencilTexture.setStroke()
Build and run. Hey presto! Your lines now look much more like a pencil’s lines:
Note: In this tutorial, you’re using a texture in a rather naive way. Brush engines in full-featured art apps are far more complex, but this approach is enough to get you started.
Note: In this tutorial, you’re using a texture in a rather naive way. Brush engines in full-featured art apps are far more complex, but this approach is enough to get you started.
To check that Pencil is tilted far enough to initiate shading, add this constant to the top of CanvasView:
private let tiltThreshold = π/6 // 30º
If you find that this value doesn’t work for you because you hold it differently, you can change its value to suit.
Note: To type π
hold down Option + P at the same time. π
is a convenience constant defined at the top of CanvasView.swift as CGFloat(M_PI)
.
When programming graphics, it’s important to start thinking in radians rather than converting to degrees and back again. Take a look at this image from Wikipedia to see the correlation between radians and degrees.
Note: To type π
hold down Option + P at the same time. π
is a convenience constant defined at the top of CanvasView.swift as CGFloat(M_PI)
.
When programming graphics, it’s important to start thinking in radians rather than converting to degrees and back again. Take a look at this image from Wikipedia to see the correlation between radians and degrees.
Next, find the following line in drawStroke(_:touch:)
:
let lineWidth = lineWidthForDrawing(context, touch: touch)
And change it to:
var lineWidth:CGFloat
if touch.altitudeAngle < tiltThreshold {
lineWidth = lineWidthForShading(context, touch: touch)
} else {
lineWidth = lineWidthForDrawing(context, touch: touch)
}
Here you're adding a check to see if your Pencil is tilted more than π/6
or 30 degrees. If yes, then you call the shading method rather than the drawing method.
Now, add this method to the bottom of CanvasView
:
private func lineWidthForShading(context: CGContext?, touch: UITouch) -> CGFloat {
// 1
let previousLocation = touch.previousLocationInView(self)
let location = touch.locationInView(self)
// 2 - vector1 is the pencil direction
let vector1 = touch.azimuthUnitVectorInView(self)
// 3 - vector2 is the stroke direction
let vector2 = CGPoint(x: location.x - previousLocation.x, y: location.y - previousLocation.y)
// 4 - Angle difference between the two vectors
var angle = abs(atan2(vector2.y, vector2.x) - atan2(vector1.dy, vector1.dx))
// 5
if angle > π {
angle = 2 * π - angle
}
if angle > π / 2 {
angle = π - angle
}
// 6
let minAngle: CGFloat = 0
let maxAngle = π / 2
let normalizedAngle = (angle - minAngle) / (maxAngle - minAngle)
// 7
let maxLineWidth: CGFloat = 60
var lineWidth = maxLineWidth * normalizedAngle
return lineWidth
}
There's some complex math in there, so here's a play-by-play:
- Store the previous touch point and the current touch point.
- Store the azimuth vector of the Pencil.
- Store the direction vector of the stroke that you're drawing.
- Calculate the angle difference between stroke line and the Pencil direction.
- Reduce the angle so it's 0 to 90 degrees. If the angle is 90 degrees, then the stroke will be the widest. Remember that all calculations are done in radians, and
π/2
is 90 degrees. - Normalize this angle between
0
and1
, where1
is 90 degrees. - Multiply the maximum line width of 60 by the normalized angle to get the correct shading width.
Note: Whenever you're working with Pencil, the following formulae come in handy:
Angle of a vector: angle = atan2(opposite, adjacent)
Normalize: normal = (value - minValue) / (maxValue - minValue)
Note: Whenever you're working with Pencil, the following formulae come in handy:
Angle of a vector: angle = atan2(opposite, adjacent)
Normalize: normal = (value - minValue) / (maxValue - minValue)
Build and run. Hold Pencil at about the angle indicated in the picture, as is you're going to shade. Without changing the angle, do a little shading.
Notice how as the stroke direction changes it becomes wider and narrower. It's a bit blobby here with this naive approach, but you can definitely see the potential.