5
votes

I am having a weird trouble with UIBezierPath where my color and the line width are not being rendered correctly.

I am drawing a series of lines (like a ruler) with major and minor gradations. The major gradations are lines with longer length...

Major Gradation: Color Red & Line Width: 2.0 Minor Gradation: Color Yellow & Line Width: 0.0 (thinnest line according to spec)

slider

    override func draw(in ctx: CGContext) {
    if let _ = cameraControlSlider {
        guard gradationValues.count >= 0 else {
            return
        }

        let frame = CGRect(x: insetBy, y: 0, width: bounds.width - insetBy, height: bounds.height)
        ctx.clear(frame)

        let startx = Int(frame.origin.x)
        let endx = Int(frame.origin.x + frame.width)

        let incrementWidth = (endx - startx) / (gradationValuesOnScreen + 1)

        var counter = 1
        var x = startx + counter * incrementWidth

        while x < endx {

            var y1 = Int(frame.origin.y)
            var y2 = Int(frame.origin.y) + Int(frame.height)
            var lineWidth: Float = 2.0
            var color = UIColor.red

            if counter % majorGradationInterval != 1 {
                y1 = Int(frame.origin.y) + Int(frame.height / 4)
                y2 = Int(frame.origin.y + frame.height) - Int(frame.height / 4)
                lineWidth = 0.0
                color = UIColor.yellow
            }

            ctx.addPath(drawLine(in: ctx, x1: x, y1: y1, x2: x, y2: y2, color: color, lineWidth: lineWidth))
            counter += 1
            x = startx + counter * incrementWidth
        }
    }
}

func drawLine(in ctx: CGContext, x1: Int, y1: Int, x2: Int, y2: Int, color: UIColor, lineWidth: Float) -> CGPath {
    let path = UIBezierPath()

    ctx.setStrokeColor(color.cgColor)

    path.lineWidth = CGFloat(lineWidth)
    path.move(to: CGPoint(x: x1, y: y1))
    path.addLine(to: CGPoint(x: x2, y: y2))
    path.close()

    ctx.strokePath()

    return path.cgPath
}

Essentially I want the longer lines to be of RED color and thicker line width but the colors look inverted and the line width doesn't seem to have any impact.

As can be seen from the draw function there is no UIColor red which is associated with the smaller line lengths. I have tried setting the stroke color before the path is created but that didn't seem to work.

1

1 Answers

2
votes

There are a couple of issues here, but the key issue is that drawLine is creating a UIBezierPath and is calling strokePath. But strokePath doesn't stroke that UIBezierPath you just created, but rather the previous path added to that context. But since you don't add the line to the context until after you return from drawLine, everything is off by one. Plus, since you're adding all those lines to the context (rather than replacing the path), you're doing a bunch of redrawing of the same lines repeatedly.

You can fix it by just calling drawLine rather than addPath(drawLine(...):

override func draw(in ctx: CGContext) {
    ...

    while x < endx {
        ...

        drawLine(in: ctx, x1: x, y1: y1, x2: x, y2: y2, color: color, lineWidth: lineWidth)

        ...
    }
}

And drawLine doesn't return anything, but actually just draws it:

func drawLine(in ctx: CGContext, x1: Int, y1: Int, x2: Int, y2: Int, color: UIColor, lineWidth: Float) {
    let path = UIBezierPath()

    ctx.saveGState()
    ctx.setStrokeColor(color.cgColor)
    ctx.setLineWidth(CGFloat(lineWidth))
    path.move(to: CGPoint(x: x1, y: y1))
    path.addLine(to: CGPoint(x: x2, y: y2))
    ctx.addPath(path.cgPath)
    ctx.strokePath()
    ctx.restoreGState()
}

Note, I'm saving and restoring the graphical context so that I can draw new line line segment path each time. Also, since I'm stroking the context's path, I'm calling setLineWidth on the context, not the path. Also, not shown here, I found that if I stroked the yellow lines with a width of zero, they didn't appear, so I used 1:

example image

Note, if this were a UIView subclass you could simplify this further (just stroke the UIBezierPath directly rather than messing around with CoreGraphics, you could make it @IBDesignable, etc.). But if you're going to implement draw(in:), I'd probably do something like the above.