Apple Pencil Basics

The Apple pencil adds more functionality to tablets than finger touch, even though you use the same code. Let’s do a quick explore of the Apple Pencil, and see how easy it is to add support in your app.
Download the starter app. It’s a Swift playground I’ll be using on my iPad so I can demo my pencil easily. I’ve made a simple app that has a label at top, a square label, with a few support methods added.
UIViewController has methods to respond to touch events.
Head down to the Touch events mark. The first of these is the touchesBegan method.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
    }

This tells the app that there have been a touch. Add the following to this method.

squareText = "Touched"
updateDisplay(touches: touches)

We’ll update everything in a method above called updateDisplay.
The second method is after a touch began, and the touch moves around on the device.

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    squareText = "Moved"
    updateDisplay(touches: touches)
    }

The last is when the touch ends.

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        squareText = "Ended"
updateDisplay(touches: touches)
    }

These methods do not yet tell the difference between touches with your finger or the Apple pencil. Run and tap the screen, drag your finger and you’ll see the square change from touch to moved to ended.


Stop the playground.

Head up to the update display method. I transferred the touches set as a parameter. This is an unordered collection of UITouch, for the case you would put down more than one finger. For a stylus there’s only one, so I’ll get the first member of this set, which I must use the first property. Since the first property is optional, I’ll unwrap it with an if let.

if let touch = touches.first{
            
        }

I get locations from UITouch’s location property. However, it will ask you what view you are touching. I’ll use the view.

location = touch.location(in: view)

The code I already have will set a property location to the location of the touch.
Run this and you can move the square around with your finger.

For a single finger touch that’s all you need. Stop the app.

Apple pencil uses the same touch API’s but adds three more methods to describe three more attributes of an apple pencil.
The tilt of the pencil is called the altitude angle.

After the location add this:

altitude = touch.altitudeAngle

The next is the AzimuthAngle, which is the physical direction of the pencil point on the device surface within a specific view.

Again, I’ll use view for this.

azimuth = touch.azimuthAngle(in: view)

Both of these are in radians. I’ve added a function to change it to degrees.
Finally there’s force describing the pressure of the pencil on the iPad. The force property is a CGFloat that if 1.0 or above is considered a firm force, below a light touch. Add this to the code.

force = touch.force
if force > 1.0 {
     squareText = "FIRM"
}

Run the code. You’ll want to open the live view to full screen.

With your finger, drag the square. The location changes, altitude stays at 90 degrees, or completely vertical. If you have an Apple Pencil, try again, and the values change. Changing the angle of the pencil changes the altitude, moving around changes the azimuth, and your pressure shows up as a force. Press enough and you change the square text to FORCE.

There’s a lot you can do with those three extra methods. While art apps will use all there for art tools think of force as a possible right click menu button. It’s not just for drawing, but a lot more.

This was a text version of the iOS Development Tips Weekly video series you can find at the Lynda.com and LinkedIn Learning libraries. The first week of a week’s tip will be available for free. After that, you will need a subscription to get access to it. Click the image at left  to view.

The Whole Code

You can find the code on GitHub here.  The code below uses a navigation controller to work around a problem on iPad Playgrounds v2.1  which causes view controllers to crash.

import UIKit
import PlaygroundSupport
class ViewController:UIViewController{
    
    //Views
    let square = UILabel()
    let statusLabel = UILabel()
    //touch object
    var touch = UITouch()
    
    var squareText = "Untouched"
    var statusText = "Touch And Pencil Demo"
    var force:CGFloat = 0.0
    var azimuth:CGFloat = 0.0
    var location = CGPoint(x: 200, y: 200)
    var squareSize = CGSize(width: 100, height: 100)
    var altitude:CGFloat = 0.0
    
    //radian conversions
    func degrees(_ radians:CGFloat)->CGFloat{
        return radians * (180.0 / CGFloat.pi)
    }
    
    func updateDisplay(touches: Set<UITouch>){
        // add your code here
        if let newTouch = touches.first{
            touch = newTouch
        }
        location = touch.location(in: view)
        altitude = touch.altitudeAngle
        azimuth = touch.azimuthAngle(in: view)
        force = touch.force 
        if force > 1.0 {
            squareText = "FIRM"
        }
        //update the labels
        statusText = String(format:"Stylus: X:%3.0f Y:%3.0f Azimuth:%2.3f(%2.1fº) Altitude:%2.3f(%2.1fº) Force:%2.3f",location.x,location.y,azimuth,degrees(azimuth),altitude,degrees(altitude),force)
        statusLabel.text = statusText
        square.frame.origin = location
        square.text = squareText
    }
    
    //MARK: - Touch events
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        squareText = "Touched"
        updateDisplay(touches: touches)
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        squareText = "Moved"
        updateDisplay(touches: touches)
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        squareText = "Ended"
        updateDisplay(touches: touches)
    }
    //MARK: -- Label configuration
    func addSquare(){
        view.addSubview(square)
        square.frame = CGRect(origin: location, size: squareSize)
        square.backgroundColor = .blue
        square.text = "untouched"
        square.textColor = .white
        square.textAlignment = .center
    }
    
    func addStatusLabel(){
        view.addSubview(statusLabel)
        statusLabel.text = "Touch and Pencil Demo"
        statusLabel.font = UIFont(name: "Menlo", size: 20)
        statusLabel.backgroundColor = UIColor.darkGray.withAlphaComponent(50)
        statusLabel.textColor = .white
        statusLabel.translatesAutoresizingMaskIntoConstraints = false
        var constraints = [NSLayoutConstraint]()
        constraints += [NSLayoutConstraint(item: statusLabel, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0.0)]
        constraints += [NSLayoutConstraint(item: statusLabel, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0.0)]
        constraints += [NSLayoutConstraint(item: statusLabel, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0.0)]
        view.addConstraints(constraints)
    }
    
    
    //MARK: -- Life Cycle 
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .gray
        addStatusLabel()
        addSquare()

        

    }
    
}

let vc = ViewController()
let rvc = UINavigationController(rootViewController: vc)
vc.navigationController?.isNavigationBarHidden = true
PlaygroundPage.current.liveView = rvc

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.