I am working on a test program to draw free-form doodles smoothed using Catmull-Rom splines. (link) See Erica Sadun's outstanding Core iOS Developer's Cookbook for a "recipe" showing how to draw smooth curves using Catmull-Rom splines. (Disclosure: I was a tech reviewer on several of Dr. Sadun's books.)
Dr. Sadun's sample code, which is written in Objective-C, draws a series of line segments between the input points as the user traces their finger, and only smooths the curve when they release their finger.
I've refactored the code in Swift 3.
I also changed the algorithm so that it generates smooth curves "live" as the user draws. The output of the smoothing algorithm is a series of points that need to be connected with tiny line segments to create the appearance of a smooth curve.
I convert the resulting "polyline" to a a UIBezierPath and draw that in my custom UIView subclass's drawRect method.
I've found that the math to create the smooth curves is actually extremely fast. What's time-consuming is drawing a big, complex UIBezierPath
. As you add a new point to the array of input points that get smoothed, the last part of the curve needs to be changed to create a smooth curve around the previous control point rather than a "kink".
As a result the easiest thing to do is to just draw the whole bezier path in each call to DrawRect. However, as the bezier path object gets larger, the app spends more and more time serving drawRect()
, and so tracing the path of the user's finger gets more and more sluggish and the points it registers from the user's movements get further and further apart.
I've implemented a number of optimizations. One of those is to call setNeedsDisplayInRect()
(Or rather setNeedsDisplay(_:)
, since this is Swift 3) to only mark the small part of the view that has actually changed as needing to be redrawn.
I was surprised to find that my drawRect()
(er, draw(_:)
function is now very fast and doesn't bog down any more.
It seems that rendering of UIBezierPath
objects in drawRect() is optimized so that it skips parts of the path that are outside of the view's "dirty region"(the clip region that actually needs updating.)
That surprised me. I expected to have to abandon doing my drawing with UIBezierPath
and to write custom code that found the parts of my drawing that intersect the current clip region, but it looks like I won't have to do that.
Can somebody confirm that Quartz drawing optimizes the drawing of UIBezierPaths inside a view's drawrect() method so they efficiently only render the parts of the path that intersect the current clipping mask?