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