Bezier doodling problem

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

I have to admit, I don't know enough to troubleshoot this. I am reasonably sure I got the translation to Swift right, but I'm not even 100% sure of that.

Could you elaborate more on this? I've been using absolute position (well, in the view's coordinate), and not sure what problem it could run into.

I tried adding a 5px uiview to the touch point, it always stays right with the touch. So maybe the math is messed up