Fill a UIView with diagonally drawn lines?

2019-02-04 22:24发布

问题:

How to fill a UIView like this (with some diagonally drawn white lines).

PS: My intentions is about the fill not the border.

Any help?

回答1:

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:

(Assuming the view has a red backgroundColor)

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



回答2:

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

amazingly, it's 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
    }

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 it the "size of the UIView". Often, you want to draw a number of the "boxes" actually within the view, at different coordinates. (A good example is for a calendar).

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

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.)



回答3:

The simplest code to draw slash lines:

import UIKit

@IBDesignable
class SlashPatternView: UIView {
@IBInspectable
var lineWidth: CGFloat = 1 { didSet { setNeedsLayout() } }

@IBInspectable
var squareSize: CGFloat = 1 { didSet { setNeedsLayout() } }

@IBInspectable
var lineColor: UIColor = .white { didSet { setNeedsLayout() }}

var slashes: UIImage {
    let size = squareSize
    let renderer = UIGraphicsImageRenderer(size: CGSize(width: size, height: size))
    return renderer.image { context in
        let cgcontext = context.cgContext

        cgcontext.addLines(between: [CGPoint(x: 0, y: size/2), CGPoint(x: size/2, y: 0)])
        cgcontext.addLines(between: [CGPoint(x: size/2, y: size), CGPoint(x: size, y: size/2)])

        cgcontext.setStrokeColor(lineColor.cgColor)
        cgcontext.setLineCap(.square)
        cgcontext.setLineWidth(lineWidth)
        cgcontext.strokePath()
    }
}
override func layoutSubviews() {
    super.layoutSubviews()
}


override func draw(_ rect: CGRect) {
    backgroundColor?.setFill()
    UIRectFill(rect)
    slashes.drawAsPattern(in: rect)
}
}


回答4:

The simplest way for me is to put a background image in your UIView (like this). The second solution is to draw the lines using Core Graphics Framework (more efficient, but more code to write).

Hope this help !