Not a full answer, just a very important gotcha that James P raised in a comment on the favourite answer:
He wrote:
I've found setting the on length to 0.01 gives you a circular dot,
whereas they are slightly elongated when using 0.
For example,
let dashes: [CGFloat] = [0.001, path.lineWidth * 2]