Fill a UIView with diagonally drawn lines?

Amazingly simple algorithm ...

Say you have these values:

    let T: CGFloat = 15     // desired thickness of lines
    let G: CGFloat = 30     // desired gap between lines
    let W = rect.size.width
    let H = rect.size.height

Remarkably, it is this simple ...

    var p = -(W > H ? W : H) - T
    while p <= W {
        
        c.move( to: CGPoint(x: p-T, y: -T) )
        c.addLine( to: CGPoint(x: p+T+H, y: T+H) )
        c.strokePath()
        p += G + T + T
    }

enter image description here

Here is a complete UIView class:

class Ruled: UIView {
    
    override func draw(_ rect: CGRect) {
        
        let T: CGFloat = 15     // desired thickness of lines
        let G: CGFloat = 30     // desired gap between lines
        let W = rect.size.width
        let H = rect.size.height
        
        guard let c = UIGraphicsGetCurrentContext() else { return }
        c.setStrokeColor(UIColor.orange.cgColor)
        c.setLineWidth(T)
        
        var p = -(W > H ? W : H) - T
        while p <= W {
            
            c.move( to: CGPoint(x: p-T, y: -T) )
            c.addLine( to: CGPoint(x: p+T+H, y: T+H) )
            c.strokePath()
            p += G + T + T
        }
    }
}

That's it !

The entire fundamental algorithm:

1. Start at the top-left, minus the longest side

2. Draw diagonals until you come to the right

Nice and easy! :)


To clip to a rectangle:

The class above simply draws one "box" the size of the UIView.

Often, you want to draw many of the "boxes" within one view, at different positions. A typical example is for a calendar.

enter image description here

This example will draw one box. Call it for each of the boxes you need to draw:

Furthermore, this example explicitly draws both stripes, rather than drawing one stripe over the background color:

func simpleStripes(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) {
     
    let stripeWidth: CGFloat = 20.0 // whatever you want
    let m = stripeWidth / 2.0
    
    guard let c = UIGraphicsGetCurrentContext() else { return }
    c.setLineWidth(stripeWidth)
    
    let r = CGRect(x: x, y: y, width: width, height: height)
    let longerSide = width > height ? width : height
    
    c.saveGState()
    c.clip(to: r)
        
        var p = x - longerSide
        while p <= x + width {
            
            c.setStrokeColor(pale blue)
            c.move( to: CGPoint(x: p-m, y: y-m) )
            c.addLine( to: CGPoint(x: p+m+height, y: y+m+height) )
            c.strokePath()
            
            p += stripeWidth
            
            c.setStrokeColor(pale gray)
            c.move( to: CGPoint(x: p-m, y: y-m) )
            c.addLine( to: CGPoint(x: p+m+height, y: y+m+height) )
            c.strokePath()
            
            p += stripeWidth
        }
        
    c.restoreGState()
}

If you want to animate them moving...

1, To offset, simply subtract from the pointer when you start it. Amazingly, nothing else needs to be changed.

  var p = x - longerSide - offset // animate offset from 0 to stripeWidth

2, Careful programmers would prefer an offset equal to the miter to avoid the "pointy top-left corner" problem:

  var p = x - longerSide - offset - m // for better-looking top-left corner

enter image description here

3, You can use any number of stripes in various colors, and indeed you can use different stripe widths in any combination. Amazingly, the algorithm still works and is safe. (If you have more than one width, just set the miter m as the maximum width.)


One way of achieving this would be to override the draw(_:) method of UIView and do your custom drawing there.

Drawing diagonal lines is fairly simple, you just need to:

  • Stride from 0 to width + height (along the horizontal edge of the rect, then up the vertical), by the gap + line width, converted from being a diagonal (at 45º) length to being parallel to the edge of the rect to draw in.

  • At each iteration, draw a line from the given point for that iteration to the point on the edge opposite (at 45º). We get this point by simply working up the vertical edge of the rect, then along the horizontal)

Something like this should achieve the desired result:

class StripeyView : UIView {

    let lineGap: CGFloat = 7
    let lineWidth: CGFloat = 3
    let lineColor = UIColor.white

    override func draw(_ rect: CGRect) {

        let ctx = UIGraphicsGetCurrentContext()!

        // flip y-axis of context, so (0,0) is the bottom left of the context
        ctx.scaleBy(x: 1, y: -1)
        ctx.translateBy(x: 0, y: -bounds.size.height)

        // generate a slightly larger rect than the view,
        // to allow the lines to appear seamless
        let renderRect = bounds.insetBy(dx: -lineWidth * 0.5, dy: -lineWidth * 0.5)

        // the total distance to travel when looping (each line starts at a point that
        // starts at (0,0) and ends up at (width, height)).
        let totalDistance = renderRect.size.width + renderRect.size.height

        // loop through distances in the range 0 ... totalDistance
        for distance in stride(from: 0, through: totalDistance,
                               // divide by cos(45º) to convert from diagonal length
                               by: (lineGap + lineWidth) / cos(.pi / 4)) {

            // the start of one of the stripes
            ctx.move(to: CGPoint(
                // x-coordinate based on whether the distance is less than the width of the
                // rect (it should be fixed if it is above, and moving if it is below)
                x: distance < renderRect.width ?
                    renderRect.origin.x + distance :
                    renderRect.origin.x + renderRect.width,

                // y-coordinate based on whether the distance is less than the width of the
                // rect (it should be moving if it is above, and fixed if below)
                y: distance < renderRect.width ?
                    renderRect.origin.y :
                    distance - (renderRect.width - renderRect.origin.x)
            ))

            // the end of one of the stripes
            ctx.addLine(to: CGPoint(
                // x-coordinate based on whether the distance is less than the height of
                // the rect (it should be moving if it is above, and fixed if it is below)
                x: distance < renderRect.height ?
                    renderRect.origin.x :
                    distance - (renderRect.height - renderRect.origin.y),

                // y-coordinate based on whether the distance is less than the height of
                // the rect (it should be fixed if it is above, and moving if it is below)
                y: distance < renderRect.height ?
                    renderRect.origin.y + distance :
                    renderRect.origin.y + renderRect.height
            ))
        }

        // stroke all of the lines added
        ctx.setStrokeColor(lineColor.cgColor)
        ctx.setLineWidth(lineWidth)
        ctx.strokePath()
    }
}

Output:

enter image description here

(Assuming the view has a red backgroundColor)

You can adjust the lineGap and lineWidth properties to generate varying results.