This one weird trick for drawing dashes

Just a quick tip before the weekend: You get dashes and you get dashes and you get dashes and…

1crpbq

Here’s the code.

let drawSize = CGSize(width: 100, height: 100)
let ovalRect = CGRect(origin: .zero, size: drawSize)
    .insetBy(dx: 5, dy: 5)

let path = UIBezierPath(ovalIn: ovalRect)
path.lineWidth = 5

// Set up dash pattern 
var dashes: [CGFloat] = [12, 2, 2, 2]

// Plug it into the Bezier path
path.setLineDash(&dashes, count: dashes.count, phase: 0)

// And draw
let image = UIGraphicsImageRenderer(size: drawSize).image {
    context in let bounds = context.format.bounds
    UIColor.white.set(); UIRectFill(bounds);
    UIColor.red.setFill(); UIColor.blue.setStroke()
    path.fill(); path.stroke()
}

image

Your (not very) weird tips:

  • Use an even number of dash values as on-off point patterns. Odd patterns don’t look right. The second time through each pattern the on-off flips and…just try it out and you’ll see what I mean.
  • Make the sum of the dashes divide cleanly into diameter * π (in this example, 90π). This prevents weirdness where the start and end meet up.
  • Make sure the dash values is var and not let, so you can pass them by using “&” instead of doing some godawful UnsafePointer<CGFloat> thing.
  • Draw the path to see your dashes. Swift playgrounds don’t show borders or dashes in QuickLook previews.

Dashes are always as thick as a path’s lineWidth and the lineWidth is centered on the exterior of the shape. In this example, 2.5 points lies outside the path, 2.5 points inside, which you can see when looking at the gaps between each dash segment.

Leave enough space so the border won’t clip by applying rect.insetBy. It’s one of the new Swiftier Core Graphics calls, and it’s much easier to read than the old CGRectInset-style calls.

image

One Comment

  • And on top of that add the animation to “rotation” key of the dashed layer. You’ll have your own famous “ants marching” animation as found in Adobe PS, Pixelmator and other graphic editors.