Заполнить UIView диагонально нарисованными линиями?

Как заполнить UIView следующим образом (с некоторыми диагонально нарисованными белыми линиями).

введите описание изображения здесь

PS: Мои намерения касаются заполнения, а не границы.

Любая помощь?

Ответ 1

Одним из способов достижения этого было бы переопределить метод draw(_:) UIView и сделать там свой собственный чертеж.

Рисование диагональных линий довольно просто, вам просто нужно:

  • Шаг от 0 до ширины + высота (вдоль горизонтального края прямоугольника, затем вверх по вертикали), шириной зазора + ширины линии, преобразованной из диагональной (на 45º) длины в параллель с край прямоугольника для рисования.

  • На каждой итерации нарисуйте линию из данной точки для этой итерации до точки на противоположном (45 °) краю. Мы получаем эту точку, просто работая по вертикальному краю прямоугольника, затем по горизонтали)

Что-то вроде этого должно достичь желаемого результата:

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()
    }
}

Вывод:

введите описание изображения здесь

(Предполагая, что представление имеет красный цвет backgroundColor)

Вы можете настроить свойства lineGap и lineWidth для генерации различных результатов.

Ответ 2

Удивительно простой алгоритм...

Скажем, у вас есть эти значения:

    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

Примечательно, что это так просто...

    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

Вот полный класс UIView:

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
        }
    }
}

Это !

Весь фундаментальный алгоритм:

1. Начните сверху слева, минус самая длинная сторона

2. Нарисуйте диагонали, пока не дойдете направо

Легко и приятно! :)


Чтобы обрезать прямоугольник:

Класс выше просто рисует одну "коробку" размером UIView.

Часто вы хотите нарисовать множество "прямоугольников" в одном представлении в разных позициях. Типичный пример для календаря.

enter image description here

Этот пример нарисует одну коробку. Назовите это для каждого из ящиков, которые вам нужно нарисовать:

Кроме того, этот пример явно рисует обе полосы, а не одну полосу поверх цвета фона:

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()
}

Если вы хотите оживить их, двигаясь...

1, чтобы сместить, просто вычтите из указателя при запуске. Удивительно, но больше ничего не нужно менять.

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

2. Осторожные программисты предпочли бы смещение, равное митре, чтобы избежать проблемы "острого верхнего левого угла":

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

enter image description here

3, Вы можете использовать любое количество полос в различных цветах, и действительно, вы можете использовать разную ширину полосы в любой комбинации. Удивительно, но алгоритм все еще работает и безопасен. (Если у вас более одной ширины, просто установите митру m в качестве максимальной ширины.)

Ответ 3

Простейший код для рисования наклонных линий:

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

Самый простой способ для меня - разместить фоновое изображение в вашем UIView (как это). Второе решение - рисовать линии с использованием Core Graphics Framework (более эффективное, но больше кода для записи).

Надеюсь на эту помощь!