Tracking Apple Pencil angles and pressure with Xamarin

Rumor has it that Apple will support the Apple Pencil in the forthcoming iPad. If so, more developers will want to use the new features of UITouch -- force, angle, and elevation -- supported by the incredibly-precise stylus.

Basically, it's trivial:

-- Force is UITouch.Force;
-- Angle is UITouch.GetAzimuthAngle(UIView); and
-- Angle above horizontal is UITouch.AltitudeAngle

(The UIView objects are there, I think, to make it easier to create a custom angular transform that is more natural to the task at hand -- i.e., an artist could "rotate" the page slightly to accommodate the angle with which they like to work. I think.)

Anyhow, here's some code:

[code lang="fsharp"]

namespace UITouch0

open System
open UIKit
open Foundation
open System.Drawing
open CoreGraphics

type ContentView(color : UIColor) as this =
inherit UIView()
do this.BackgroundColor \<- color

let MaxRadius = 200.0
let MaxStrokeWidth = nfloat 10.0

//Mutable!
member val Circle : (CGPoint * nfloat * nfloat * nfloat ) option = None with get, set

member this.DrawTouch (touch : UITouch) =
let radius = (1.0 - (float touch.AltitudeAngle) / (Math.PI / 2.0)) * MaxRadius |> nfloat
this.Circle \<- Some (touch.LocationInView(this), radius, touch.GetAzimuthAngle(this), touch.Force)
this.SetNeedsDisplay()

override this.Draw rect =

match this.Circle with
| Some (location, radius, angle, force) ->
let rectUL = new CGPoint(location.X - radius, location.Y - radius)
let rectSize = new CGSize(radius * (nfloat 2.0), radius * (nfloat 2.0))
use g = UIGraphics.GetCurrentContext()
let strokeWidth = force * MaxStrokeWidth
g.SetLineWidth(strokeWidth)
let hue = angle / nfloat (Math.PI * 2.0)
let color = UIColor.FromHSB(hue, nfloat 1.0, nfloat 1.0)
g.SetStrokeColor(color.CGColor)
g.AddEllipseInRect \<| new CGRect(rectUL, rectSize)
g.MoveTo (location.X, location.Y)
let endX = location.X + nfloat (cos(float angle)) * radius
let endY = location.Y + nfloat (sin(float angle)) * radius
g.AddLineToPoint (endX, endY)
g.StrokePath()
| None -> ignore()

type SimpleController() =
inherit UIViewController()
override this.ViewDidLoad() =
this.View \<- new ContentView(UIColor.Blue)

override this.TouchesBegan(touches, evt) =
let cv = this.View :?> ContentView

touches |> Seq.map (fun o -> o :?> UITouch) |> Seq.iter cv.DrawTouch

override this.TouchesMoved(touches, evt) =
let cv = this.View :?> ContentView
touches |> Seq.map (fun o -> o :?> UITouch) |> Seq.iter cv.DrawTouch

[\<Register("AppDelegate")>]
type AppDelegate() =
inherit UIApplicationDelegate()
let window = new UIWindow(UIScreen.MainScreen.Bounds)

override this.FinishedLaunching(app, options) =
let viewController = new SimpleController()
viewController.Title \<- "F# Rocks"
let navController = new UINavigationController(viewController)
window.RootViewController \<- navController
window.MakeKeyAndVisible()
true

module Main =
[\<EntryPoint>]
let main args =
UIApplication.Main(args, null, "AppDelegate")
0

[/code]

And it looks like this:

[video width="600" height="800" mp4="/uploads/2016/03/ScreenFlow-2.mp4"][/video]