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.

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

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:

Apple Pencil Tutorial

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 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:

Apple Pencil Tutorial

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.

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:

Apple Pencil Tutorial

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:

Apple Pencil Tutorial

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.

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:

  1. Store the previous touch point and the current touch point.
  2. Store the azimuth vector of the Pencil.
  3. Store the direction vector of the stroke that you're drawing.
  4. Calculate the angle difference between stroke line and the Pencil direction.
  5. 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.
  6. Normalize this angle between 0 and 1, where 1 is 90 degrees.
  7. 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)

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.

Apple Pencil Tutorial

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.