I was trying to follow this tutorial about drawing strokes in which the line width varies with the speed of the touch. Advanced Freehand Drawing Techniques
I attempted to convert the code to Swift, and it mostly works. There's something wrong with the way the path is placed in the view that I can't understand. When "drawing" near the top of the screen it seems to work sort of as expected, but drawing towards the bottom of the screen the path increasingly goes really far from the touch point. And it is drawing lots of hills and valleys instead of lines.
I have gone over every line of code many times, and can't see what is going wrong.
Here is my translation for the first part of the tutorial, for the NaiveVarWidthView class.
import UIKit
class NaiveVarWidthView: UIView {
var path: UIBezierPath?
var incrementalImage: UIImage?
var pts = [CGPoint].init(repeating: CGPoint.zero, count: 5)
var ctr: Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
initializeView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeView()
}
func initializeView() {
self.backgroundColor = UIColor(white: 0.5, alpha: 1)
isMultipleTouchEnabled = false
path = UIBezierPath()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
ctr = 0
let touch = touches.first! as UITouch
pts[0] = touch.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch
let p = touch.location(in: self)
ctr += 1
pts[ctr] = p
if (ctr == 4) {
pts[3] = CGPoint(x: (pts[2].x + pts[4].x) / 2.0, y: (pts[2].y + pts[4].y / 2.0))
path?.move(to: pts[0])
path?.addCurve(to: pts[3], controlPoint1: pts[1], controlPoint2: pts[2])
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
if (incrementalImage == nil) {
let rectpath = UIBezierPath(rect: self.bounds)
UIColor(white: 0.5, alpha: 1).setFill()
rectpath.fill()
}
incrementalImage?.draw(at: .zero)
UIColor.black.setStroke()
var speed: Float = 0.0
for i in 0..<3 {
let dx = pts[i + 1].x - pts[i].x
let dy = pts[i + 1].y - pts[i].y
speed += sqrtf(Float(dx * dx + dy * dy))
}
let FUDGE_FACTOR = 100
let width = Float(FUDGE_FACTOR) / speed
path?.lineWidth = CGFloat(width)
path?.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
setNeedsDisplay()
path?.removeAllPoints()
pts[0] = pts[3]
pts[1] = pts[4]
ctr = 1
}
}
override func draw(_ rect: CGRect) {
incrementalImage?.draw(in: rect)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setNeedsDisplay()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesEnded(touches, with: event)
}
}
Here is the code I translated from
#import "NaiveVarWidthView.h"
@implementation NaiveVarWidthView
{
UIBezierPath *path;
UIImage *incrementalImage;
CGPoint pts[5];
uint ctr;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setMultipleTouchEnabled:NO];
path = [UIBezierPath bezierPath];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
ctr = 0;
UITouch *touch = [touches anyObject];
pts[0] = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
ctr++;
pts[ctr] = p;
if (ctr == 4)
{
pts[3] = CGPointMake((pts[2].x + pts[4].x)/2.0, (pts[2].y + pts[4].y)/2.0);
[path moveToPoint:pts[0]];
[path addCurveToPoint:pts[3] controlPoint1:pts[1] controlPoint2:pts[2]];
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0); // ................. (1)
if (!incrementalImage)
{
UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds];
[[UIColor whiteColor] setFill];
[rectpath fill];
}
[incrementalImage drawAtPoint:CGPointZero];
[[UIColor blackColor] setStroke];
float speed = 0.0;
for (int i = 0; i < 3; i++)
{
float dx = pts[i+1].x - pts[i].x;
float dy = pts[i+1].y - pts[i].y;
speed += sqrtf(dx * dx + dy * dy);
} // ................. (2)
#define FUDGE_FACTOR 100 // emperically determined
float width = FUDGE_FACTOR/speed; // ................. (3)
[path setLineWidth:width];
[path stroke];
incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setNeedsDisplay];
[path removeAllPoints]; // ................. (4)
pts[0] = pts[3];
pts[1] = pts[4];
ctr = 1;
}
}
- (void)drawRect:(CGRect)rect
{
[incrementalImage drawInRect:rect];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self setNeedsDisplay];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
@end